Section II: Data Exploration (R)
setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
marketclose_ts <- ts(data_in$MARKETCLOSE, start=date_ts[1])
#print(marketclose_ts)
plot.ts(marketclose_ts)

library(xts)
marketclose_xts <- xts(x=data_in$MARKETCLOSE,order.by = date_ts)
autoplot(marketclose_xts)

# Split marketclose by week and calculate averages per week
weekly_marketclose_xts <- split(marketclose_xts, f = "weeks")
print(head(weekly_marketclose_xts[[1]]))
[,1]
2002-07-01 109.86
2002-07-02 109.76
2002-07-03 109.83
print(head(weekly_marketclose_xts[[2]]))
[,1]
2002-07-08 109.18
2002-07-09 109.27
2002-07-10 109.80
2002-07-11 110.06
2002-07-12 110.42
dates_weekly_endpoints <- xts::endpoints(marketclose_xts,on='weeks')
readings_per_week <- dates_weekly_endpoints[-c(1)]-dates_weekly_endpoints[-c(length(dates_weekly_endpoints))]
# print weeks with less than 5 values per week
for (weekID in 1:length(weekly_marketclose_xts)) {
if (length(weekly_marketclose_xts[[weekID]][,1]) < 5) {
week_len <- length(weekly_marketclose_xts[[weekID]])
week_endpoint_ID <- dates_weekly_endpoints[1+weekID]
print(paste("WeekID:",weekID,", num_readings:",week_len))
print(paste("Week_start:",as.character(date_ts[week_endpoint_ID-week_len+1]),
", Week_end:",as.character(date_ts[week_endpoint_ID]) ) )
}
}
[1] "WeekID: 1 , num_readings: 3"
[1] "Week_start: 2002-07-01 , Week_end: 2002-07-03"
[1] "WeekID: 10 , num_readings: 4"
[1] "Week_start: 2002-09-03 , Week_end: 2002-09-06"
[1] "WeekID: 22 , num_readings: 3"
[1] "Week_start: 2002-11-25 , Week_end: 2002-11-27"
[1] "WeekID: 26 , num_readings: 4"
[1] "Week_start: 2002-12-23 , Week_end: 2002-12-27"
[1] "WeekID: 27 , num_readings: 4"
[1] "Week_start: 2002-12-30 , Week_end: 2003-01-03"
[1] "WeekID: 30 , num_readings: 4"
[1] "Week_start: 2003-01-21 , Week_end: 2003-01-24"
[1] "WeekID: 34 , num_readings: 4"
[1] "Week_start: 2003-02-18 , Week_end: 2003-02-21"
[1] "WeekID: 42 , num_readings: 4"
[1] "Week_start: 2003-04-14 , Week_end: 2003-04-17"
[1] "WeekID: 48 , num_readings: 4"
[1] "Week_start: 2003-05-27 , Week_end: 2003-05-30"
[1] "WeekID: 53 , num_readings: 4"
[1] "Week_start: 2003-06-30 , Week_end: 2003-07-03"
[1] "WeekID: 62 , num_readings: 4"
[1] "Week_start: 2003-09-02 , Week_end: 2003-09-05"
[1] "WeekID: 74 , num_readings: 3"
[1] "Week_start: 2003-11-24 , Week_end: 2003-11-26"
[1] "WeekID: 78 , num_readings: 3"
[1] "Week_start: 2003-12-22 , Week_end: 2003-12-24"
[1] "WeekID: 79 , num_readings: 3"
[1] "Week_start: 2003-12-29 , Week_end: 2003-12-31"
[1] "WeekID: 82 , num_readings: 4"
[1] "Week_start: 2004-01-20 , Week_end: 2004-01-23"
[1] "WeekID: 86 , num_readings: 4"
[1] "Week_start: 2004-02-17 , Week_end: 2004-02-20"
[1] "WeekID: 101 , num_readings: 4"
[1] "Week_start: 2004-06-01 , Week_end: 2004-06-04"
[1] "WeekID: 102 , num_readings: 4"
[1] "Week_start: 2004-06-07 , Week_end: 2004-06-10"
[1] "WeekID: 106 , num_readings: 4"
[1] "Week_start: 2004-07-06 , Week_end: 2004-07-09"
[1] "WeekID: 115 , num_readings: 4"
[1] "Week_start: 2004-09-07 , Week_end: 2004-09-10"
[1] "WeekID: 126 , num_readings: 3"
[1] "Week_start: 2004-11-22 , Week_end: 2004-11-24"
[1] "WeekID: 130 , num_readings: 4"
[1] "Week_start: 2004-12-20 , Week_end: 2004-12-23"
[1] "WeekID: 131 , num_readings: 4"
[1] "Week_start: 2004-12-27 , Week_end: 2004-12-30"
[1] "WeekID: 134 , num_readings: 4"
[1] "Week_start: 2005-01-18 , Week_end: 2005-01-21"
[1] "WeekID: 139 , num_readings: 4"
[1] "Week_start: 2005-02-22 , Week_end: 2005-02-25"
[1] "WeekID: 143 , num_readings: 4"
[1] "Week_start: 2005-03-21 , Week_end: 2005-03-24"
[1] "WeekID: 153 , num_readings: 4"
[1] "Week_start: 2005-05-31 , Week_end: 2005-06-03"
[1] "WeekID: 158 , num_readings: 4"
[1] "Week_start: 2005-07-05 , Week_end: 2005-07-08"
[1] "WeekID: 167 , num_readings: 4"
[1] "Week_start: 2005-09-06 , Week_end: 2005-09-09"
[1] "WeekID: 178 , num_readings: 3"
[1] "Week_start: 2005-11-21 , Week_end: 2005-11-23"
[1] "WeekID: 183 , num_readings: 4"
[1] "Week_start: 2005-12-27 , Week_end: 2005-12-30"
[1] "WeekID: 184 , num_readings: 4"
[1] "Week_start: 2006-01-03 , Week_end: 2006-01-06"
[1] "WeekID: 186 , num_readings: 4"
[1] "Week_start: 2006-01-17 , Week_end: 2006-01-20"
[1] "WeekID: 191 , num_readings: 4"
[1] "Week_start: 2006-02-21 , Week_end: 2006-02-24"
[1] "WeekID: 198 , num_readings: 4"
[1] "Week_start: 2006-04-10 , Week_end: 2006-04-13"
[1] "WeekID: 205 , num_readings: 4"
[1] "Week_start: 2006-05-30 , Week_end: 2006-06-02"
[1] "WeekID: 210 , num_readings: 3"
[1] "Week_start: 2006-07-05 , Week_end: 2006-07-07"
[1] "WeekID: 235 , num_readings: 4"
[1] "Week_start: 2006-12-26 , Week_end: 2006-12-29"
[1] "WeekID: 236 , num_readings: 4"
[1] "Week_start: 2007-01-02 , Week_end: 2007-01-05"
weekly_avg_marketclose <- lapply(X = weekly_marketclose_xts, function(X){return(mean(X))})
all_weeks <- ts(unlist(weekly_avg_marketclose))
filtered_weeks <- all_weeks
filtered_weeks[readings_per_week<5] <- NaN
#filtered_weeks <- ts(unlist(weekly_avg_marketclose)[readings_per_week==5])
closes <- cbind(all_weeks, filtered_weeks)
autoplot(closes)

#autoplot(ts(unlist(weekly_avg_marketclose)))
It would be interesting to see effects of holidays/long weekends,
annual events (eg superbowl, quad witching). We will ignore them for
now. Other factors include sector specific sentiment or whole market
sentiment, which hopefully decomposition below will uncover.
Number of daily readings by year:
table(format(date_ts,"%Y"))
2002 2003 2004 2005 2006 2007
126 250 250 251 252 44
Number of weekly readings by year:
table(format(date_ts[dates_weekly_endpoints[-c(1)]],"%Y"))
2002 2003 2004 2005 2006 2007
26 53 52 52 52 9
Number of trading days per trading week (avg) by year
table(format(date_ts,"%Y"))/table(format(date_ts[dates_weekly_endpoints[-c(1)]],"%Y"))
2002 2003 2004 2005 2006 2007
4.846154 4.716981 4.807692 4.826923 4.846154 4.888889
Number of trading days per month by year
dates_monthly_endpoints <- xts::endpoints(marketclose_xts,on='months')
table(format(date_ts[dates_monthly_endpoints[-c(1)]],"%Y"))
2002 2003 2004 2005 2006 2007
6 12 12 12 12 3
table(format(date_ts,"%Y"))/table(format(date_ts[dates_monthly_endpoints[-c(1)]],"%Y"))
2002 2003 2004 2005 2006 2007
21.00000 20.83333 20.83333 20.91667 21.00000 14.66667
table(format(date_ts,"%m/%Y",start=("07/2002")))
01/2003 01/2004 01/2005 01/2006 01/2007 02/2003 02/2004 02/2005 02/2006 02/2007 03/2003
21 19 20 20 22 19 19 19 19 20 21
03/2004 03/2005 03/2006 03/2007 04/2003 04/2004 04/2005 04/2006 05/2003 05/2004 05/2005
23 22 23 2 21 22 21 19 21 20 21
05/2006 06/2003 06/2004 06/2005 06/2006 07/2002 07/2003 07/2004 07/2005 07/2006 08/2002
22 21 21 22 22 21 22 21 20 19 22
08/2003 08/2004 08/2005 08/2006 09/2002 09/2003 09/2004 09/2005 09/2006 10/2002 10/2003
21 22 23 23 20 21 21 21 21 23 23
10/2004 10/2005 10/2006 11/2002 11/2003 11/2004 11/2005 11/2006 12/2002 12/2003 12/2004
21 21 22 19 18 20 20 22 21 21 21
12/2005 12/2006
21 20
mean(table(format(date_ts,"%m/%Y")))
[1] 20.57895
table(format(date_ts,"%Y"))
2002 2003 2004 2005 2006 2007
126 250 250 251 252 44
Last reading date for each year:
date_ts[xts::endpoints(marketclose_xts,on='years')[-c(1)]]
[1] "2002-12-31" "2003-12-31" "2004-12-30" "2005-12-30" "2006-12-29" "2007-03-02"
Year 2003,04,05,06 seasonal trends:
weekly_avg_marketclose_2003456 <- ts(as.numeric(unlist(weekly_avg_marketclose))[(26+1):(26+53+52+52+52)],frequency=52)
#library(forecast)
no_transform_2003456 <- stl(log(weekly_avg_marketclose_2003456), s.window=53)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
autoplot(no_transform_2003456)

#TimeSeriesWeeklyDecomposed<-stl(ts_data , s.window="periodic")
Seasonal trends:
library(forecast)
This is forecast 8.16
Need help getting started? Try the online textbook FPP:
http://otexts.com/fpp2/
no_transform_2003456 %>% seasonal() %>% ggsubseriesplot()

no_transform_2003456 %>% remainder() %>% autoplot()

rm(list=ls())
setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
marketclose_ts <- ts(data_in$MARKETCLOSE, start=date_ts[1],frequency = 251.625)
plot.ts(marketclose_ts)

Given the irregular nature of the time series we use xts modeling and
estimate daily returns %
library(xts)
marketclose_xts <- xts(x=data_in$MARKETCLOSE,order.by = date_ts)
marketclose_ret <- 100*diff.xts(log(marketclose_xts))
#marketclose_ret <- as.zoo(marketclose_ret[-c(1)])
plot.zoo(marketclose_ret, ylab = "Daily returns (%)", main = "Percentage daily returns")
# lowess fit with the f = 1/average # trading days in a month
# Calculation for 20.8 follow below
lines(index(marketclose_ret), lowess(marketclose_ret[,1], f = 1/20.8)$y, col = "red", lwd = 2)

# Volatility as function of month
plot(jitter(as.numeric(format(index(marketclose_ret),"%m")), amount = 1/3), marketclose_ret, pch = 20, ylab = "Daily percent returns", xlab = "Month", bty = "l")

boxplot(as.vector(marketclose_ret) ~ factor(quarters(index(marketclose_ret),abbreviate = TRUE),labels=c("Q3","Q4","Q1","Q2")), xlab = "Quarter", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

boxplot(as.vector(marketclose_ret) ~ factor(months(index(marketclose_ret),abbreviate = TRUE), levels = c("Jul","Aug","Sep","Oct","Nov","Dec","Jan","Feb","Mar","Apr","May","Jun")), xlab = "Month", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),
levels = c("Mon","Tue","Wed","Thu","Fri")) ,xlab = "Day of the week", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

myplot <- boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))*factor(months(index(marketclose_ret),abbreviate = TRUE),levels = c("Jul","Aug","Sep","Oct","Nov","Dec","Jan","Feb","Mar","Apr","May","Jun")),
xlab = "Day of the week in given month", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

myplot <- boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))*factor(quarters(index(marketclose_ret),abbreviate = TRUE),levels = c("Q3","Q4","Q1","Q2")),
xlab = "Day of the week in given year", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

myplot <- boxplot(as.vector(marketclose_ret) ~ factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))*factor(format(index(marketclose_ret),"%Y"),levels = c("2002","2003","2004","2005","2006","2007")),
xlab = "Day of the week in given year", pch = 20, col = rgb(0, 0, 0, 0.4), ylab = "Daily percent returns", bty = "l")
grid(col=1,lwd=1.5)

Monday returns as function of return on friday return in prev week by
year
#head(which(factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))=="Mon"))
#head(which(factor(weekdays(index(marketclose_ret),abbreviate = TRUE),levels = c("Mon","Tue","Wed","Thu","Fri"))=="Fri"))
monday_indices <- which(weekdays(index(marketclose_ret),abbreviate = TRUE)=="Mon")
monday_indices <- monday_indices[-c(1)]
plot(as.vector(marketclose_ret[monday_indices])~as.vector(marketclose_ret[(monday_indices-1)]),
xlab="Prev day change",ylab="Monday change")
lines(lowess(as.vector(marketclose_ret[monday_indices])~as.vector(marketclose_ret[(monday_indices-1)]),f=1/2),col="red")
grid(col=1,lwd=1.5)

plot.ts(as.vector(marketclose_ret[(monday_indices)]),ylim=c(-1.5,1.5),
xlab="Date",ylab = "% Change")
#lines(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),col=2)
lines(lowess(as.vector(marketclose_ret[(monday_indices-1)]),f=1/2),col="green",type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices)]),f=1/2),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices-2)]),f=1/2),col="blue",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(marketclose_ret[(monday_indices-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices)])),f=1/2),col="black",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),f=1/2),col="orange",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-2)])),f=1/2),col="yellow",ylim=c(-1,1),type="o")
#lines(sign(as.numeric(marketclose_ret)[monday_indices]))
grid(col=1,lwd=1.5)

plot.ts(as.vector(marketclose_ret[(monday_indices)]),ylim=c(-1.5,1.5),
xlab="Date",ylab = "% Change")
#lines(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),col=2)
lines(lowess(as.vector(marketclose_ret[(monday_indices-1)]),f=1/4),col="green",type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices)]),f=1/4),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices-2)]),f=1/4),col="blue",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(marketclose_ret[(monday_indices-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices)])),f=1/4),col="black",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),f=1/4),col="orange",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-2)])),f=1/2),col="yellow",ylim=c(-1,1),type="o")
#lines(sign(as.numeric(marketclose_ret)[monday_indices]))
grid(col=1,lwd=1.5)

plot.ts(as.vector(marketclose_ret[(monday_indices)]),ylim=c(-1.5,1.5),
xlab="Date",ylab = "% Change")
#lines(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),col=2)
lines(lowess(as.vector(marketclose_ret[(monday_indices-1)]),f=1/8),col="green",type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices)]),f=1/8),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices-2)]),f=1/8),col="blue",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(marketclose_ret[(monday_indices-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices)])),f=1/8),col="black",ylim=c(-1,1),type="o")
lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-1)])),f=1/8),col="orange",ylim=c(-1,1),type="o")
#lines(lowess(as.vector(100*diff.xts(log(marketclose_xts)[(monday_indices-2)])),f=1/2),col="yellow",ylim=c(-1,1),type="o")
#lines(sign(as.numeric(marketclose_ret)[monday_indices]))
grid(col=1,lwd=1.5)

Even with f=1/8 it seems Monday daily returns will follow strictly
downward trend
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-1),1173)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-2),1172)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-3),1171)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices-4),1170)]),f=1/8)$y,15)
#tail(lowess(as.vector(marketclose_ret[c((monday_indices))]),f=1/8)$y,15)
plot(tail(lowess(as.vector(marketclose_ret[c((monday_indices-1),1173)]),f=1/8)$y,50),ylim=c(-1,1),type='l',main="trend 50 day returns by day of the week",ylab="% Change",xlab="day")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices-2),1172)]),f=1/8)$y,50),col="blue")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices-3),1171)]),f=1/8)$y,50),col="green")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices-4),1170)]),f=1/8)$y,50),col="orange")
lines(tail(lowess(as.vector(marketclose_ret[c((monday_indices))]),f=1/8)$y,50),col="red")
grid(col=1,lwd=1.5)

plot(c(rep(0,50),rollmean(as.vector(marketclose_ret),50)),ylim=c(-1,1),type='l',main="50 & 200 day MA of daily returns",ylab="% Change",xlab="day",col="red")
lines(c(rep(0,200),rollmean(as.vector(marketclose_ret),200)),col="green")
#lines(as.vector(marketclose_ret))
lines(lowess(marketclose_ret[,1], f = 1/20.8)$y, lwd = 2)
#lines(rollmean(as.vector(marketclose_ret[c((monday_indices-2),1172)]),50),col="blue")
#lines(rollmean(as.vector(marketclose_ret[c((monday_indices-3),1171)]),50),col="green")
#lines(rollmean(as.vector(marketclose_ret[c((monday_indices-4),1170)]),50),col="orange")
#lines(rollmean(as.vector(marketclose_ret[monday_indices]),50),col="red")
grid(col=1,lwd=1.5)

plot(rollmean(as.vector(marketclose_ret[c((monday_indices-1),1173)]),50),ylim=c(-.25,.25),type='l',main="50 week MA of weekly returns by day of the week",ylab="% Change",xlab="day")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-2),1172)]),50),col="blue")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-3),1171)]),50),col="green")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-4),1170)]),50),col="orange")
lines(rollmean(as.vector(marketclose_ret[monday_indices]),50),col="red")
grid(col=1,lwd=1.5)

plot(rollmean(as.vector(marketclose_ret[c((monday_indices-1),1173)]),200),ylim=c(-.25,.25),type='l',main="200 week MA returns by day of the week",ylab="% Change",xlab="day")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-2),1172)]),200),col="blue")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-3),1171)]),200),col="green")
lines(rollmean(as.vector(marketclose_ret[c((monday_indices-4),1170)]),200),col="orange")
lines(rollmean(as.vector(marketclose_ret[monday_indices]),200),col="red")
grid(col=1,lwd=1.5)

monday return wrt prev monday
indices_2007 <- which(as.numeric(format(index(marketclose_ret),"%Y")) %in% c(2006,2007))
monday_indices_2007 <- monday_indices[monday_indices %in% indices_2007]
plot(as.vector(marketclose_ret[monday_indices_2007])~as.vector(marketclose_ret[(monday_indices_2007-1)]),
xlab="Prev day change 2006-2007",ylab="Monday change 2006-2007")
lines(lowess(as.vector(marketclose_ret[monday_indices_2007])~as.vector(marketclose_ret[(monday_indices_2007-1)]),f=1/2),col="red")
grid(col=1,lwd=1.5)

#plot.zoo(xts(sign(as.numeric(marketclose_ret)[monday_indices_2007])*sign(as.numeric(marketclose_ret)[(monday_indices_2007-1)]),order.by = index(marketclose_ret)[monday_indices_2007]),main= "Sign of Monday change times sign of prev day")
#lines(xts(sign(as.numeric(marketclose_ret)[monday_indices_2007]),order.by = index(marketclose_ret)[monday_indices_2007]),col="red")
#lines(sign(as.numeric(marketclose_ret)[monday_indices_2007]))
plot(lowess(as.vector(marketclose_ret[(monday_indices_2007-1)]),f=1/2),col="green",ylim=c(-1,1),
xlab="Date",ylab = "% Change")
lines(lowess(as.vector(marketclose_ret[(monday_indices_2007)]),f=1/2),col="red",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices_2007-2)]),f=1/2),col="blue",ylim=c(-1,1),type="o")
lines(lowess(as.vector(marketclose_ret[(monday_indices_2007-3)]),f=1/2),col="pink",ylim=c(-1,1),type="o")
lines(sign(as.numeric(marketclose_ret)[monday_indices_2007]))
grid(col=1,lwd=1.5)

Plot of features
setwd("~/Documents/interview_prep/Question1")
data_in_cleaned <- read.csv(file = 'DataSet_rmNA.csv')
date_ts_cleaned <- as.Date(data_in_cleaned$DATE, format = "%Y-%m-%d")
marketclose_ts_cleaned <- ts(data_in_cleaned$MARKETCLOSE, start=date_ts_cleaned[1],frequency = 251.625)
plot.ts(marketclose_ts_cleaned)

library(xts)
marketclose_xts_cleaned <- xts(x=data_in_cleaned$MARKETCLOSE,order.by = date_ts_cleaned)
marketclose_ret_cleaned <- 100*diff.xts(log(marketclose_xts_cleaned))
## marketclose_ret_cleaned <- marketclose_ret_cleaned[-c(1)]
#marketclose_ret <- as.zoo(marketclose_ret[-c(1)])
plot.zoo(marketclose_ret_cleaned, ylab = "Daily returns (%)", main = "Percentage daily returns")
# lowess fit with the f = 1/average # trading days in a month
# Calculation for 20.8 follow below
lines(index(marketclose_ret_cleaned), lowess(marketclose_ret_cleaned[,1], f = 1/20.8)$y, col = "red", lwd = 2)
feature1_xts <- xts(x=data_in_cleaned$FEATURE_1,order.by = date_ts_cleaned)
feature1_diff_xts <- diff.xts((feature1_xts))
## feature1_diff_xts <- feature1_diff_xts[-c(1)]
lines(index(feature1_diff_xts), lowess(feature1_diff_xts[,1], f = 1/20.8)$y, col = "blue", lwd = 2)
feature2_xts <- xts(x=data_in_cleaned$FEATURE_2,order.by = date_ts_cleaned)
feature2_diff_xts <- diff.xts((feature2_xts))
lines(index(feature2_diff_xts), lowess(feature2_diff_xts[,1], f = 1/20.8)$y, col = "green", lwd = 2)
## feature2_diff_xts <- feature2_diff_xts[-c(1)]
feature3_xts <- xts(x=data_in_cleaned$FEATURE_3,order.by = date_ts_cleaned)
feature3_scaled_xts <- ((feature3_xts))/100
lines(index(feature3_scaled_xts), lowess(feature3_scaled_xts[,1], f = 1/20.8)$y, col = "orange", lwd = 2)
## feature3_scaled_xts <- feature3_scaled_xts[-c(1)]
binaryf_xts <- xts(x=data_in_cleaned$BINARY_FEATURE,order.by = date_ts_cleaned)
## binaryf_xts <- binaryf_xts[-c(1)]
lines(index(binaryf_xts),binaryf_xts, col = "yellow", lwd = 2)

Cross corr between time seriers
title_logret <- "Daily log returns (%)"
par(mfrow = c(2, 2))
TSA::acf(marketclose_ret_cleaned, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(marketclose_ret_cleaned, na.action = na.pass,lag.max = 100, main = "")
# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Daily abs log returns (%)"
TSA::acf(abs(marketclose_ret_cleaned), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(marketclose_ret_cleaned), na.action = na.pass, lag.max = 100, main = "")

# (P)ACF of squared daily returns
#par(mfrow = c(1, 2))
#TSA::acf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
#pacf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
title_logret <- "Feature1 log returns"
par(mfrow = c(2, 2))
TSA::acf(feature1_diff_xts, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(feature1_diff_xts, na.action = na.pass,lag.max = 100, main = "")
# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Feature1 abs log returns"
TSA::acf(abs(feature1_diff_xts), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(feature1_diff_xts), na.action = na.pass, lag.max = 100, main = "")

title_logret <- "Feature2 log returns"
par(mfrow = c(2, 2))
TSA::acf(feature2_diff_xts, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(feature2_diff_xts, na.action = na.pass,lag.max = 100, main = "")
# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Feature2 abs log returns"
TSA::acf(abs(feature2_diff_xts), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(feature2_diff_xts), na.action = na.pass, lag.max = 100, main = "")

title_logret <- "Feature3 log returns"
par(mfrow = c(2, 2))
TSA::acf(feature3_scaled_xts, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(feature3_scaled_xts, na.action = na.pass,lag.max = 100, main = "")
# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Feature3 abs log returns"
TSA::acf(abs(feature3_scaled_xts), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(feature3_scaled_xts), na.action = na.pass, lag.max = 100, main = "")

Plot ret vs f1 f2 f3
par(mfrow =c(1,3) )
marketclose_ret_cleaned_ <- marketclose_ret_cleaned[-c(1)]
feature1_diff_xts_ <- feature1_diff_xts[-c(1)]
plot(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature1_diff_xts_) ,xlab="Feature1",
ylab="Daily % log returns")
lines(lowess(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature1_diff_xts_), f=0.1),
col="red")
feature2_diff_xts_ <- feature2_diff_xts[-c(1)]
plot(as.numeric(marketclose_ret_cleaned) ~ as.numeric(feature2_diff_xts) ,xlab="Feature2",
ylab="Daily % log returns")
lines(lowess(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature2_diff_xts_), f=0.1),
col="red")
feature3_scaled_xts_ <- feature3_scaled_xts[-c(1)]
plot(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature3_scaled_xts_) ,xlab="Feature3",
ylab="Daily % log returns")
lines(lowess(as.numeric(marketclose_ret_cleaned_) ~ as.numeric(feature3_scaled_xts_), f=0.1),
col="red")

sel_fit <- auto.arima(marketclose_ret_cleaned_, xreg = cbind(feature2_diff_xts_ ,feature3_scaled_xts_))
plot(resid(sel_fit))

Box.test(resid(sel_fit), lag=100, type="Ljung")
Box-Ljung test
data: resid(sel_fit)
X-squared = 187.87, df = 100, p-value = 2.508e-07
qqnorm(resid(sel_fit, type="innovation"))

marketclose_ts <- ts(as.numeric(data_in_cleaned$MARKETCLOSE), start=date_ts_cleaned[1],frequency = 251.625)
marketclose_ts_decomp <- stl(log(marketclose_ts), s.window=21, robust=TRUE)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
forecast::autoplot(marketclose_ts_decomp)

#TimeSeriesWeeklyDecomposed<-stl(ts_data , s.window="periodic")
feature1_ts <- ts(as.numeric(feature1_xts), start=date_ts[1],frequency = 251.625)
feature1_ts_decomp <- stl(log(feature1_ts), s.window="periodic", robust=TRUE)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
autoplot(feature1_ts_decomp)

feature2_ts <- ts(as.numeric(feature2_xts), start=date_ts[1],frequency = 251.625)
feature2_ts_decomp <- stl(log(feature2_ts), s.window="periodic", robust=TRUE)
#no_transform_2003456 <- mstl(weekly_avg_marketclose_2003456)
autoplot(feature2_ts_decomp)

#monthplot(marketclose_ts_decomp,choice="seasonal")
#plot(seasonal(feature1_ts_decomp))
#lines(seasonal(marketclose_ts_decomp),col=2)
plot(forecast::remainder(marketclose_ts_decomp))
feature1_ts <- ts(data_in_cleaned$FEATURE_1,start = date_ts_cleaned[1],frequency = 251.625)
#lines(-(feature1_ts)/100,col=2)
feature2_ts <- ts(data_in_cleaned$FEATURE_2,start = date_ts_cleaned[1],frequency = 251.625)
#lines(-(feature2_ts)/100,col="green")
feature3_ts <- ts(data_in_cleaned$FEATURE_3,start = date_ts_cleaned[1],frequency = 251.625)
lines(((feature3_ts)/10000),col="blue")

#lines(((feature3_ts/1000))^{1/3}/10,col="blue")
#lines(remainder(marketclose_ts_decomp))
resid_decomp <- forecast::remainder(marketclose_ts_decomp)
resid_decomp_logret <- diff((resid_decomp))
title_logret <- "Daily log returns (%)"
par(mfrow = c(2, 2))
TSA::acf(resid_decomp_logret, na.action = na.pass, lag.max = 100, main = title_logret)
pacf(resid_decomp_logret, na.action = na.pass,lag.max = 100, main = "")
# (P)ACF of absolute value of daily returns
#par(mfrow = c(1, 2))
title_abslogret <- "Daily abs log returns (%)"
TSA::acf(abs(resid_decomp_logret), na.action = na.pass, lag.max = 100, main = title_abslogret)
pacf(abs(resid_decomp_logret), na.action = na.pass, lag.max = 100, main = "")

# (P)ACF of squared daily returns
#par(mfrow = c(1, 2))
#TSA::acf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
#pacf(I(marketclose_ret_cleaned^2), na.action = na.pass, main = title_sp)
plot(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature2_ts)),xlim=c(-1,1))
lines(lowess(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature2_ts)), f=1/5),
col="red")
grid(col=1,lwd=1.5)

plot(as.numeric(resid_decomp_logret) ~ as.numeric((feature3_ts)/100)[-c(1)],xlim=c(-1,1))
lines(lowess(as.numeric(resid_decomp_logret) ~ as.numeric((feature3_ts)/100)[-c(1)], f=1/5),
col="red")
grid(col=1,lwd=1.5)

plot(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature1_ts)),xlim=c(-1,1))
lines(lowess(as.numeric(resid_decomp_logret) ~ as.numeric(diff(feature1_ts)), f=1/5),
col="red")
grid(col=1,lwd=1.5)

for_stl <- forecast(marketclose_ts_decomp, h = 21)
plot(for_stl)

exp(for_stl$mean)
Time Series:
Start = 11884.6299056135
End = 11884.7093889717
Frequency = 251.625
[1] 137.1074 137.8653 137.4644 137.7676 138.6953 138.5616 139.3502 139.1598 138.8544
[10] 137.4894 136.3955 136.3485 136.0867 135.9724 136.1907 136.4010 137.3733 137.1660
[19] 137.2883 136.3747 136.3359
tail(marketclose_ts)
Time Series:
Start = 11884.6060606061
End = 11884.6259314456
Frequency = 251.625
[1] 136.65 137.24 135.97 137.53 137.55 137.07
Section III: Model Development and analysis (R)
Daily Volatility based Trend Following and Mean Reversion
Switching
(Please clear the environment and all variables, starting with a
clean slate from here.)
I haven’t considered the provided features in the data for this model
development since I don’t know what they represent, how they were
extracted and whether or not they were lagged by one trading day.
Further it seems all of them are endogenous (since they seem to be
extracted from the price data), so they shouldn’t have any extra
‘information’ (in information theoretical sense of the word) even though
they might be better predictors. However, here I focus on
interpretability of the strategy in terms of variables well known in
financial trading.
To enable interpretability of the model, I use well-defined features
or features representing well-defined quantities related to a stock
price eg. volatility, moving averages, relative strength index etc. The
model essentially switches between following a trend and mean reversion
based on the stock volatility (relative to its historical values).
The basic strategy is this: when the stock volatility is high
relative to its usual volatility, I use mean reversion strategy based on
RSI(2). When the volatility is low I follow the trend based on MA. In
addition in slow volatility regime if the RSI(2) gets very high, this
indicates oversold condition in which case I short. I further show
improvement by updating the threshold parameters every 5 days by
identifying best model parameters using grid search. Performance is
judged on the basis of cumulative PNL wrt always buy strategy. The
prediction for next also improves and is -1 from the updating
strategy.
This strategy can be potentially further improved by using momentum,
which I expect to be particularly strong during downturns. But it has to
be balanced against being ‘too-late’ trying to time the stock price.
Another potential improvement is to use volatility forecast using a
Markov switching based GARCH model (since they seem to have been shown
more accurate in predicting short term volatility <week than general
GARCH models) or GARCH-ARIMA model. I use R here instead of python,
however a small snippet of code to cleaup the data is in python3.
rm(list=ls())
## Setup
## Get some functions/packages
## install.packages("RCurl")
require(RCurl)
sit = getURLContent('https://github.com/systematicinvestor/SIT/raw/master/sit.gz', binary=TRUE, followlocation = TRUE, ssl.verifypeer = FALSE)
con = gzcon(rawConnection(sit, 'rb'))
source(con)
close(con)
## Get data, this is original file downloaded without any changes
setwd("~/Documents/interview_prep/Question1")
data_in <- read.csv(file = 'DataSet.csv')
date_ts <- as.Date(data_in$DATE, format = "%m/%d/%Y")
library(xts)
marketclose_xts <- xts(data_in$MARKETCLOSE,order.by = date_ts)
marketclose_ret <- 100*diff(log(marketclose_xts))
#Estimate historical relative volatility
library(quantmod)
ret.log <- ROC(marketclose_xts, type='continuous')
hist.vol <- runSD(ret.log, n = 21)
vol.rank <- percent.rank(SMA(percent.rank(hist.vol, 252), 21), 250)
## Mean reversion feature
rsi2_values <- RSI(marketclose_xts,2)
## Trend following feature
sma.short <- SMA(marketclose_xts, 5)
sma.long <- SMA(marketclose_xts, 20)
## Trading strategy: High Volatility - Mean reversion, Low volatility - follow trend
# long if vol.rank > 0.50 and rsi2_values < 50 (mean reversion) short otherwise
# short if vol.rank < 0.50 and
# rsi2_values > 80 (overbought) or 5 week MA < 20 week MA
# long otherwise
# Add lag to predict next value from current estimates
#
# `iif` is more stable version of `ifelse()` function in R to gracefully handle `NA` and `Inf` entries
sig <- Lag(iif(vol.rank > 0.50,
iif(rsi2_values < 50 , 1, -1),
iif(sma.short < sma.long | rsi2_values > 80 , -1, 1)
))
## Evaluate
# We evaluate the strategy against always buy strategy in column CUMSUM in data. Momentum is just the difference of successive entries. Noting here that sig is delayed by 1 trading day.
print('CUMSUM column from data:')
print(head(data_in$CUMSUM[-c(1)]))
print("Always buy strategy")
ret <- 1000*momentum(marketclose_xts)*1
print(head(cumsum(ret[-c(1)])))
## Plot cumulative PNL
ret <- 1000*momentum(marketclose_xts)*sig
eq <- (cumsum(ret[-c(1)]))
plot(coredata(eq)~date_ts[-c(1)],type='l',xlab= "Year", ylab = "Cumulative PNL")
grid(col=1,lwd=1)
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
legend(x="topleft",legend=c("Volatility based switching","Buy Daily (Given)"),
fill=c("black","red"))
## Compare cumulative PNL at the end
print(data.frame('Given Strategy'=tail(data_in$CUMSUM[-c(1)]),'Proposed Strategy'=tail(as.vector(eq)),row.names = as.character(tail(date_ts))))
## Optimize thresholding parameters using grid search:
# Parameters: vol.rank_MIN, rsi2_values_MAX, rsi2_values_MIN
# sig.test <- (iif(vol.rank.test > vol.rank_MIN,
# iif((rsi2_values.test < rsi2_values_MAX) , 1, -1),
# iif(sma.short.test < sma.long.test | ((rsi2_values.test > rsi2_values_MIN)), -1, 1)
# we will use foreach for faster for loops
# automatic install of packages if they are not installed already
list.of.packages <- c(
"foreach",
"doParallel",
"ranger",
"palmerpenguins",
"tidyverse",
"kableExtra"
)
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
if(length(new.packages) > 0){
install.packages(new.packages, dep=TRUE)
}
#loading packages
for(package.i in list.of.packages){
suppressPackageStartupMessages(
library(
package.i,
character.only = TRUE
)
)
}
# Create subset arrays to test how much these parameters change over time
# Every 5 day update of parameters
sub_ser.len <- 5
start_point <- 700
Tmax_array <- seq(start_point,length(marketclose_xts),sub_ser.len)
# Grid search for parametrs that maximize cumulative return over subset array
# and store the prediction for next day
return_max.test <- matrix(-Inf,length(Tmax_array),1)
predict_array.test <- matrix(1,length(Tmax_array),1)
max_par.test <- matrix(0,length(Tmax_array),3)
# Read from saved file for retesting
max_par.test <- read.csv("max_par.test_5day700_30_rsi2MINrange.csv")[,c("V1","V2","V3")]
print("Starting grid search. This might take a while (5-10 minutes)")
foreach (i = 1:length(Tmax_array)) %do% {
print(paste("Updating threshold parameters for the week",i,"of",length(Tmax_array)))
Tmax <- Tmax_array[i]
marketclose_xts.test <- marketclose_xts[1:Tmax]
marketclose_ret.test <- 100*diff(log(marketclose_xts.test))[-c(1)]
#Historical volatility
ret.log.test = ROC(marketclose_xts.test, type='continuous')
hist.vol.test = runSD(ret.log.test, n = 21)
vol.rank.test = percent.rank(SMA(percent.rank(hist.vol.test, 252), 21), 250)
# Mean reversion
rsi2_values.test = RSI(marketclose_xts.test,2)
# Trend following
sma.short.test <- SMA(marketclose_xts.test, 5)
sma.long.test <- SMA(marketclose_xts.test, 20)
# Grid search
foreach (vol.rank_MIN = seq(0.1,0.8,0.01)) %do% {
for (rsi2_values_MAX in seq(30,80,2)) {
for (rsi2_values_MIN in 80){ #seq(30,80,10)) { # No effect of changes
# Prediction signal calculation
sig.test <- (iif(vol.rank.test > vol.rank_MIN,
iif((rsi2_values.test < rsi2_values_MAX) , 1, -1),
iif(sma.short.test < sma.long.test | ((rsi2_values.test > rsi2_values_MIN)), -1, 1)
))
# Save this for future use
predict.test <- tail(sig.test,1)
# Lag signal by one trading day
sig.test <- Lag(sig.test)
ret.test <- 1000*momentum(marketclose_xts.test)*sig.test
return_total.test <- tail(cumsum(ret.test[-c(1)]),1)
# Save if Cumulative return is more than current maximum
if (return_total.test > return_max.test[i]) {
return_max.test[i] <- return_total.test
max_par.test[i,1] <- vol.rank_MIN
max_par.test[i,2] <- rsi2_values_MAX
max_par.test[i,3] <- rsi2_values_MIN
predict_array.test[i] <- predict.test
sig.test.max <- sig.test
}
}
}
}
}
# Save for fututre reuse
# write.csv(max_par.test,"max_par.test_5day700_30_rsi2MINrange.csv")
# Print optimized parameters for each sub time series
print(max_par.test)
optimized.pars_5days <- data.frame('vol.rank_MIN'=max_par.test[,1],'rsi2_values_MAX'=max_par.test[,2],'rsi2_values_MIN'=max_par.test[,3],row.names = date_ts[Tmax_array])
print(optimized.pars_5days)
# We will use values (0.67,40,80) for dates after and not including 2004-09-10 and before and including 2004-11-19 and so on.
optimized.pars_5days <- data.frame('vol.rank_MIN'=rep(max_par.test[1:length(Tmax_array),1],each=sub_ser.len),'rsi2_values_MAX'=rep(max_par.test[1:length(Tmax_array),2],each=sub_ser.len),'rsi2_values_MIN'=rep(max_par.test[1:length(Tmax_array),3],each=sub_ser.len))
print(dim((optimized.pars_5days)))
# pre-pend values for first 700 days
initial.pars_700days <- data.frame('vol.rank_MIN'=rep(.50,each=start_point),'rsi2_values_MAX'=rep(50,each=start_point),'rsi2_values_MIN'=rep(80,each=start_point))
optimized.pars_5days <- rbind(initial.pars_700days,optimized.pars_5days)
optimized.pars_5days <- optimized.pars_5days[1:length(marketclose_xts),]
print(head(optimized.pars_5days))
print(tail(optimized.pars_5days))
## Evaluate performance of new parameters
## Optimized trading signal
sig_opt <- Lag(iif(vol.rank > optimized.pars_5days$vol.rank_MIN,
iif(rsi2_values < optimized.pars_5days$rsi2_values_MAX , 1, -1),
iif(sma.short < sma.long | rsi2_values > optimized.pars_5days$rsi2_values_MIN, -1, 1)
))
##Plot of cumulative returns
ret_opt <- 1000*momentum(marketclose_xts)*sig_opt
eq_opt <- (cumsum(ret_opt[-c(1)]))
plot(coredata(eq_opt)~date_ts[-c(1)],type='l',col="green",xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq)~date_ts[-c(1)])
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5day update sw: Live trading + backtesting","Initail par switching backtesting","Buy Daily (Given)"),
fill=c("green","black","red"))
#Clearly new parameters perform better
# We can also use the entirety of time series for backtesting, to see what could have been the performance if we knew the best parameter estimates in the past (or try hit and trial/other optimization to improve overall performance with single set of parameters for all time points) and use it to further improve the update approach
sig_opt_optimal <- Lag(iif(vol.rank > 0.2,
iif(rsi2_values < 36 , 1, -1),
iif(sma.short < sma.long | rsi2_values > 80, -1, 1)
))
ret_opt_optimal <- 1000*momentum(marketclose_xts)*sig_opt_optimal
eq_opt_optimal <- (cumsum(ret_opt_optimal[-c(1)]))
plot(coredata(eq_opt_optimal)~date_ts[-c(1)],col="blue",type='l',xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq_opt)~date_ts[-c(1)],col="green")
lines(coredata(eq)~date_ts[-c(1)])
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5day update sw Livetrading + Backtesting","Initial par switching: Backtesting","Buy Daily (Given)","Optimal par: Backtesting"),
fill=c("green","black","red","blue"))
#Compare cumulative PNL at the end after improvements
print(data.frame('Given Strategy'=tail(data_in$CUMSUM[-c(1)]),'FixParSwitching'=tail(as.vector(eq)),'5dayUpdateSw'=tail(as.vector(eq_opt)),'Optimal'=tail(as.vector(eq_opt_optimal)),row.names = as.character(tail(date_ts))))
# Predictions for next day using these parameters:
# Prediction from optimal will be same as opt since the parameters are the same
# on last day
lastday_row <- length(marketclose_ret)
prediction_fixed <- iif(vol.rank[lastday_row] > 0.50,
iif(rsi2_values[lastday_row] < 50 , 1, -1),
iif(sma.short[lastday_row] < sma.long[lastday_row] | rsi2_values[lastday_row] > 80 , -1, 1)
)
prediction_opt <- iif(vol.rank[lastday_row] > optimized.pars_5days$vol.rank_MIN[lastday_row],
iif(rsi2_values[lastday_row] < optimized.pars_5days$rsi2_values_MAX[lastday_row] , 1, -1),
iif(sma.short[lastday_row] < sma.long[lastday_row] | rsi2_values > optimized.pars_5days$rsi2_values_MIN[lastday_row], -1, 1)
)
# Fixed parameter switching fails to predict correctly but 5 day update parameter update does correctly predict -1
print(paste0("Prediction for next day: ",prediction_opt))
This was also evident in the exploration plots for weekday specific
trends on returns
Section III: Model Development and analysis: Part II
Weekday Specific Update
[Using weekday specific daily return volatility (weekly bars) for TF
and MR switching]
## Exploiting week of the day patterns in returns
marketclose_weekday <- base::weekdays(date_ts,abbreviate=TRUE)
## Modeling Monday returns
marketclose_ret_Mon <- marketclose_ret[marketclose_weekday == "Mon"]
plot(x=date_ts[marketclose_weekday == "Mon"],y=as.vector(marketclose_ret_Mon),ylim=c(-1.5,1.5),
xlab="Year",ylab = "% Change",type = 'l',col="pink",
main="% Weekly change in Monday daily log returns")
lines(lowess(as.vector(marketclose_ret_Mon) ~ date_ts[marketclose_weekday == "Mon"] ,f=1/2),col="red",lwd=1)
lines(lowess(as.vector(marketclose_ret_Mon) ~ date_ts[marketclose_weekday == "Mon"],f=1/4),col="blue",lwd=1)
lines(lowess(as.vector(marketclose_ret_Mon) ~ date_ts[marketclose_weekday == "Mon"],f=1/8),col="green",lwd=1)
lines(lowess(as.vector(marketclose_ret_Mon) ~ date_ts[marketclose_weekday == "Mon"],f=1/12),col="black",lwd=1)
grid(col=1,lwd=1)
legend(x = "bottomleft",legend=c("% log ret", "f=1/2 lowess","f=1/4", "f=1/8","f=1/2"),
fill = c("pink","red","blue","green","black"), bty='n')
## Monday returns clearly show a visible pattern, we can create a time series for returns and use its predictability to improve the earlier forecast
## Switching strategy based on Monday returns:
## Mean reversion
rsi2_values_Mon = RSI(marketclose_ret_Mon/100,2)
## Trend following
sma.short_Mon <- SMA(marketclose_ret_Mon/100, 2)
sma.long_Mon <- SMA(marketclose_ret_Mon/100, 5)
## Volatility in weekly bars of daily returns on Mondays
# Note that we had multiplied the changes by 100 when calculating `marketclose_ret`
library(quantmod)
ret.log_Mon = marketclose_ret_Mon/100
hist.vol_Mon = runSD(ret.log_Mon, n = 5)
vol.rank_Mon = percent.rank(SMA(percent.rank(hist.vol_Mon, 52), 5), 50)
## Switching strategy for Monday returns
sig_Mon_ret <- (Lag(Lag(iif(vol.rank_Mon > 0.50,
iif((rsi2_values_Mon < 50) , 1, -1),
iif(sma.short_Mon < sma.long_Mon | ((rsi2_values_Mon > 80)), -1, 1)
))))
## Cumulative PNL plots for predicting daily returns on Mondays
ret_Mon <- 1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"]*sig_Mon_ret
eq_Mon <- (cumsum(ret_Mon[-c(1,2)]))
x_var_Mon <- (date_ts[marketclose_weekday == "Mon"])[-c(1,2)]
plot(as.numeric(eq_Mon)~x_var_Mon,type='l',xlab="Year",ylab="Cumulative returns on Mondays",
ylim=c(-10000,30000))
eq_opt_Mon <- as.numeric( cumsum( (ret_opt[marketclose_weekday == "Mon"])[-c(1,2)]) )
lines(eq_opt_Mon ~ x_var_Mon,col="green")
eg_givenStr <- as.numeric( cumsum( (1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"])[-c(1,2)]) )
lines(eg_givenStr ~ x_var_Mon,col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("Monday prediction","Combined switching","Buy every Monday (Given)"),
fill=c("black","green","red"))
## Optimizing parameters
# Create subset arrays to test how much these parameters change over time
# Every 10 week update of parameters
sub_ser.len.Mon <- 5
start_point.Mon <- 140
Tmax_array.Mon <- seq(start_point.Mon,length(marketclose_ret_Mon),sub_ser.len.Mon)
# Grid search for parameters that maximize cumulative return over subset time series
# and store the prediction for next day
return_max.test.Mon <- matrix(-Inf,length(Tmax_array.Mon),1)
predict_array.test.Mon <- matrix(1,length(Tmax_array.Mon),1)
max_par.test.Mon <- matrix(0,length(Tmax_array.Mon),3)
# Or directly read from here
max_par.test.Mon <- read.csv("max_par.test.Mon.csv")[,c("V1","V2","V3")]
print("Starting grid search. This might take a while (5-10 minutes)")
foreach (i = 1:length(Tmax_array.Mon)) %do% {
print(paste("Updating threshold parameters for the 5 week Monday time series",i,"of",length(Tmax_array.Mon)))
Tmax.Mon <- Tmax_array.Mon[i]
marketclose_ret_Mon.test <- marketclose_ret_Mon[1:Tmax.Mon]
# Differences in returns on Mondays
marketclose_ret_ret_Mon.test <- 100*diff((marketclose_ret_Mon.test))[-c(1)]
## Volatility in weekly returns
ret.log.test.Mon <- marketclose_ret_Mon.test/100
hist.vol.test.Mon <- runSD(ret.log.test.Mon, n = 5)
vol.rank.test.Mon <- percent.rank(SMA(percent.rank(hist.vol.test.Mon, 52), 5), 50)
## Mean reversion
rsi2_values.test.Mon = RSI(marketclose_ret_Mon.test/100,2)
## Trend following
sma.short.test.Mon <- SMA(marketclose_ret_Mon.test/100, 2)
sma.long.test.Mon <- SMA(marketclose_ret_Mon.test/100, 5)
# Grid search for Monday parameters
foreach (vol.rank.Mon_MIN = seq(0.3,0.8,0.01)) %do% {
foreach (rsi2_values.Mon_MAX = seq(30,80,2)) %do% {
for (rsi2_values.Mon_MIN in 60){ #seq(30,80,5)) %do%{ # doesn't change
sig.test.Mon <- (iif(vol.rank.test.Mon > vol.rank.Mon_MIN,
iif((rsi2_values.test.Mon < rsi2_values.Mon_MAX) , 1, -1),
iif(sma.short.test.Mon < sma.long.test.Mon | ((rsi2_values.test.Mon > rsi2_values.Mon_MIN)), -1, 1)
))
predict.test.Mon <- tail(sig.test.Mon,1)
sig.test.Mon <- Lag(Lag(sig.test.Mon))
ret.test.Mon <- 1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"]*sig.test.Mon
#ret.test.Mon <- 1000*momentum(marketclose_ret_Mon.test)*sig.test.Mon
return_total.test.Mon <- tail(cumsum(ret.test.Mon[-c(1,2)]),1)
#Optimize cumulative return on Mondays
if (return_total.test.Mon > return_max.test.Mon[i]) {
return_max.test.Mon[i] <- return_total.test.Mon
max_par.test.Mon[i,1] <- vol.rank.Mon_MIN
max_par.test.Mon[i,2] <- rsi2_values.Mon_MAX
max_par.test.Mon[i,3] <- rsi2_values.Mon_MIN
predict_array.test.Mon[i] <- predict.test.Mon
sig.test.Mon.max <- sig.test.Mon
}
}
}
}
}
#write.csv(max_par.test.Mon,"max_par.test.Mon.csv")
print(max_par.test.Mon)
# We use the optimal parameters for Monday update (0.44,62,60), to correct the earlier model
## Switching strategy for Monday returns
sig_opt.Mon <- Lag(Lag(iif(vol.rank_Mon > 0.44,
iif((rsi2_values_Mon < 62) , 1, -1),
iif(sma.short_Mon < sma.long_Mon | ((rsi2_values_Mon > 60)), -1, 1)
)))
##Plot of cumulative returns on Mondays after and before update
plot(as.numeric(eq_Mon)~x_var_Mon,type='l',xlab="Year",ylab="Cumulative returns on Mondays",
ylim=c(-10000,30000))
lines(eq_opt_Mon ~ x_var_Mon,col="green")
lines(eg_givenStr ~ x_var_Mon,col="red")
ret_Mon_opt <- 1000*momentum(marketclose_xts)[marketclose_weekday == "Mon"]*sig_opt.Mon
eq_Mon_opt <- as.numeric( cumsum( (ret_Mon_opt)[-c(1,2)]) )
lines(eq_Mon_opt ~ x_var_Mon,col="blue")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5 week Monday update + 5 day update","Monday prediction","5 day update","Buy every Monday (Given)"),
fill=c("blue","black","green","red"))
# Correcting the earlier plots
sig_opt_optimal.MonCorrection <- sig_opt_optimal
sig_opt_optimal.MonCorrection[marketclose_weekday == "Mon"][-c(1,2)] <- sig_opt.Mon[-c(1,2)]
ret_opt_optimal.MonCorrection <- 1000*momentum(marketclose_xts)[-c(1,2)]*sig_opt_optimal.MonCorrection
eq_opt_optimal.MonCorrection <- as.numeric( cumsum( (ret_opt_optimal.MonCorrection) ))
#Cumulative PNL plots for complete series after monday correction
plot(c(NA,eq_opt_optimal.MonCorrection)~date_ts[-c(1)],col="orange",type='l', ylim=c(0,140000),xlab= "Year", ylab = "Cumulative PNL")
lines(coredata(eq_opt_optimal)~date_ts[-c(1)],col="blue")
lines(coredata(eq_opt)~date_ts[-c(1)],col="green")
lines(coredata(eq)~date_ts[-c(1)])
lines(data_in$CUMSUM[-c(1)]~date_ts[-c(1)],col="red")
grid(col=1,lwd=1)
legend(x="topleft",legend=c("5day update sw","Fixed par switching","Buy Daily (Given)","Optimal","Optimal+Monday updates"),
fill=c("green","black","red","blue","orange"))
# Similarly we can check for weekday patterns on other days and also potential monthly or quarterly patterns
## Since next day on which prediction is needed also falls on a Monday, we can also verify the prediction from Monday series only:
lastweek_row.Mon <- length(marketclose_ret_Mon)
prediction_fixed.Mon <- iif(vol.rank_Mon[lastweek_row.Mon] > 0.50,
iif(rsi2_values_Mon[lastweek_row.Mon] < 50 , 1, -1),
iif((sma.short_Mon[lastweek_row.Mon] < sma.long_Mon[lastweek_row.Mon]) | (rsi2_values_Mon[lastweek_row.Mon] > 80) , -1, 1)
)
prediction_opt <- iif(vol.rank_Mon[lastweek_row.Mon] > 0.44,
iif(rsi2_values_Mon[lastweek_row.Mon] < 62 , 1, -1),
iif((sma.short_Mon[lastweek_row.Mon] < sma.long_Mon[lastweek_row.Mon]) | (rsi2_values_Mon[lastweek_row.Mon] > 60), -1, 1)
)
# Again fixed parameter switching fails to predict correctly but 2 week parameter update does correctly predict -1
print(paste0("Prediction for next day (2 week parameter update): ",prediction_opt))
Compare cumulative PNL at the end after Monday correction
print(data.frame('Given Strategy'=tail(data_in$CUMSUM[-c(1)]),'FixParSwitching'=tail(as.vector(eq)),'5dayUpdateSw'=tail(as.vector(eq_opt)),'Optimal'=tail(as.vector(eq_opt_optimal)),'Optimal&MonUpdate'=tail(as.vector(eq_opt_optimal.MonCorrection)),row.names = as.character(tail(date_ts))))
write.csv(cbind("sig_opt_optimal.MonCorrection"=as.numeric(sig_opt_optimal.MonCorrection),"sig_opt_optimal"=as.numeric(sig_opt_optimal),"sig_opt"=as.numeric(sig_opt),'sig'=as.numeric(sig)),"trading_signals.csv",quote=FALSE,row.names = date_ts)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCiMjIFNlY3Rpb24gMDogQW5zd2VycyB0byBxdWVzdGlvbnMKCiMjIyBRdWVzdGlvbiAxCiMjIyBGaW5hbCBQcmVkaWN0aW9uIE1vZGVsIChEZWNpc2lvbiBUcmVlKToKYGBge3J9CiMgUXVlc3Rpb24gMQojIEZpbmFsIFByZWRpY3Rpb24gTW9kZWwgKERlY2lzaW9uIFRyZWUpOgoKcmVxdWlyZShSQ3VybCkKc2l0ID0gZ2V0VVJMQ29udGVudCgnaHR0cHM6Ly9naXRodWIuY29tL3N5c3RlbWF0aWNpbnZlc3Rvci9TSVQvcmF3L21hc3Rlci9zaXQuZ3onLCBiaW5hcnk9VFJVRSwgZm9sbG93bG9jYXRpb24gPSBUUlVFLCBzc2wudmVyaWZ5cGVlciA9IEZBTFNFKQpjb24gPSBnemNvbihyYXdDb25uZWN0aW9uKHNpdCwgJ3JiJykpCnNvdXJjZShjb24pCmNsb3NlKGNvbikKCiMjIEdldCBkYXRhLCB0aGlzIGlzIG9yaWdpbmFsIGZpbGUgZG93bmxvYWRlZCB3aXRob3V0IGFueSBjaGFuZ2VzCnNldHdkKCJ+L0RvY3VtZW50cy9pbnRlcnZpZXdfcHJlcC9RdWVzdGlvbjEiKQpkYXRhX2luIDwtIHJlYWQuY3N2KGZpbGUgPSAnRGF0YVNldC5jc3YnKQpkYXRlX3RzIDwtIGFzLkRhdGUoZGF0YV9pbiREQVRFLCBmb3JtYXQgPSAiJW0vJWQvJVkiKQpsaWJyYXJ5KHh0cykKbWFya2V0Y2xvc2VfeHRzIDwtIHh0cyhkYXRhX2luJE1BUktFVENMT1NFLG9yZGVyLmJ5ID0gZGF0ZV90cykKbWFya2V0Y2xvc2VfcmV0IDwtIDEwMCpkaWZmKGxvZyhtYXJrZXRjbG9zZV94dHMpKQoKI0VzdGltYXRlIGhpc3RvcmljYWwgcmVsYXRpdmUgdm9sYXRpbGl0eQpsaWJyYXJ5KHF1YW50bW9kKQpyZXQubG9nIDwtIFJPQyhtYXJrZXRjbG9zZV94dHMsIHR5cGU9J2NvbnRpbnVvdXMnKQpoaXN0LnZvbCA8LSBydW5TRChyZXQubG9nLCBuID0gMjEpCnZvbC5yYW5rIDwtIHBlcmNlbnQucmFuayhTTUEocGVyY2VudC5yYW5rKGhpc3Qudm9sLCAyNTIpLCAyMSksIDI1MCkKCiMjIE1lYW4gcmV2ZXJzaW9uIGZlYXR1cmUKcnNpMl92YWx1ZXMgPC0gUlNJKG1hcmtldGNsb3NlX3h0cywyKQoKIyMgVHJlbmQgZm9sbG93aW5nIGZlYXR1cmUKc21hLnNob3J0IDwtICBTTUEobWFya2V0Y2xvc2VfeHRzLCA1KQpzbWEubG9uZyA8LSBTTUEobWFya2V0Y2xvc2VfeHRzLCAyMCkKCnNpZ19vcHRfb3B0aW1hbCA8LSBMYWcoaWlmKHZvbC5yYW5rID4gMC4yLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHJzaTJfdmFsdWVzIDwgMzYgICwgMSwgLTEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0IDwgc21hLmxvbmcgfCByc2kyX3ZhbHVlcyA+IDgwLCAtMSwgMSkKKSkKcmV0X29wdF9vcHRpbWFsIDwtIDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKSpzaWdfb3B0X29wdGltYWwKIyBWZXJpZnkgc2lnX29wdF9vcHRpbWFsPTEgZ2l2ZXMgcmV0X29wdF9vcHRpbWFsPSBtYXJrZXQgcG5sCiMgaXQgc2VlbXMgbW9tZW50dW0gZnVuY3Rpb24gZGl2aWRlcyB0aGUgZGlmZmVyZW5jZSBieSAxMAplcV9vcHRfb3B0aW1hbCA8LSAoY3Vtc3VtKHJldF9vcHRfb3B0aW1hbFstYygxKV0pKQoKbWFya2V0Y2xvc2Vfd2Vla2RheSA8LSBiYXNlOjp3ZWVrZGF5cyhkYXRlX3RzLGFiYnJldmlhdGU9VFJVRSkKIyMgTW9kZWxpbmcgTW9uZGF5IHJldHVybnMKbWFya2V0Y2xvc2VfcmV0X01vbiA8LSBtYXJrZXRjbG9zZV9yZXRbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0KIyMgTW9uZGF5IHJldHVybnMgY2xlYXJseSBzaG93IGEgdmlzaWJsZSBwYXR0ZXJuLCB3ZSBjYW4gY3JlYXRlIGEgdGltZSBzZXJpZXMgZm9yIHJldHVybnMgYW5kIHVzZSBpdHMgcHJlZGljdGFiaWxpdHkgdG8gaW1wcm92ZSB0aGUgZWFybGllciBmb3JlY2FzdAojIyBTd2l0Y2hpbmcgc3RyYXRlZ3kgYmFzZWQgb24gTW9uZGF5IHJldHVybnM6CgojIyBNZWFuIHJldmVyc2lvbgpyc2kyX3ZhbHVlc19Nb24gPSBSU0kobWFya2V0Y2xvc2VfcmV0X01vbi8xMDAsMikKCiMjIFRyZW5kIGZvbGxvd2luZwpzbWEuc2hvcnRfTW9uIDwtIFNNQShtYXJrZXRjbG9zZV9yZXRfTW9uLzEwMCwgMikKc21hLmxvbmdfTW9uIDwtICBTTUEobWFya2V0Y2xvc2VfcmV0X01vbi8xMDAsIDUpCmxpYnJhcnkocXVhbnRtb2QpCnJldC5sb2dfTW9uID0gbWFya2V0Y2xvc2VfcmV0X01vbi8xMDAKaGlzdC52b2xfTW9uID0gcnVuU0QocmV0LmxvZ19Nb24sIG4gPSA1KQp2b2wucmFua19Nb24gPSBwZXJjZW50LnJhbmsoU01BKHBlcmNlbnQucmFuayhoaXN0LnZvbF9Nb24sIDUyKSwgNSksIDUwKQoKc2lnX29wdC5Nb24gPC0gIExhZyhMYWcoaWlmKHZvbC5yYW5rX01vbiA+IDAuNDQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKChyc2kyX3ZhbHVlc19Nb24gPCA2MikgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0X01vbiA8IHNtYS5sb25nX01vbiB8ICgocnNpMl92YWx1ZXNfTW9uID4gNjApKSwgLTEsIDEpCikpKQoKc2lnX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb24gPC0gc2lnX29wdF9vcHRpbWFsCnNpZ19vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uW21hcmtldGNsb3NlX3dlZWtkYXkgPT0gIk1vbiJdWy1jKDEsMildIDwtIHNpZ19vcHQuTW9uWy1jKDEsMildCgpyZXRfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cylbLWMoMSwyKV0qc2lnX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb24KZXFfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbiA8LSBhcy5udW1lcmljKCBjdW1zdW0oIChyZXRfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbikgKSkKCmxhc3RkYXlfcm93IDwtIGxlbmd0aChtYXJrZXRjbG9zZV9yZXQpCgpwcmVkaWN0aW9uX29wdCA8LSBpaWYodm9sLnJhbmtbbGFzdGRheV9yb3ddID4gMC4yLCAKICAgICAgICAgICAgICAgICAgICAgIGlpZihyc2kyX3ZhbHVlc1tsYXN0ZGF5X3Jvd10gPCAzNiAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0W2xhc3RkYXlfcm93XSA8IHNtYS5sb25nW2xhc3RkYXlfcm93XSB8IHJzaTJfdmFsdWVzID4gODAsIC0xLCAxKQopCgojIFByZWRpY3RzIC0xCnByaW50KHBhc3RlMCgiUHJlZGljdGlvbiBmb3IgbmV4dCBkYXk6ICAiLHByZWRpY3Rpb25fb3B0KSkKYGBgCgojIFF1ZXN0aW9uIDIKIyBNb2RlbCBjdW11bGF0aXZlIFBOTCB2cyBtYXJrZXQgUE5MCgpgYGB7cn0KCgpwbG90KGMoTkEsZXFfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbil+ZGF0ZV90c1stYygxKV0sY29sPSJyZWQiLHR5cGU9J2wnLCB5bGltPWMoMCwxNDAwMDApLHhsYWI9ICJZZWFyIiwgeWxhYiA9ICJDdW11bGF0aXZlIFBOTCIpCmxpbmVzKGNvcmVkYXRhKGVxX29wdF9vcHRpbWFsKSB+IGRhdGVfdHNbLWMoMSldLGNvbD0iYmx1ZSIpCmxpbmVzKGRhdGFfaW4kQ1VNU1VNWy1jKDEpXX5kYXRlX3RzWy1jKDEpXSxjb2w9ImJsYWNrIikKZ3JpZChjb2w9MSxsd2Q9MSkKbGVnZW5kKHg9InRvcGxlZnQiLGxlZ2VuZD1jKCJNb2RlbCBDdW11bGF0aXZlIFBOTCAoTW9uZGF5IHRyZW5kIGNvcnJlY3Rpb24pIEJhY2t0ZXN0aW5nIiwiTW9kZWwgQ3VtdWxhdGl2ZSBQTkw6IEJhY2t0ZXN0aW5nIiwiTWFya2V0IEN1bXVsYXRpdmUgUE5MIiksCiAgICAgICBmaWxsPWMoInJlZCIsImJsdWUiLCJibGFjayIpKQpgYGAKCgoKCiMgTW9kZWwgRGV2ZWxvcG1lbnQKCiMjICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNlY3Rpb24gSTogRGF0YSBDbGVhbmluZyAoUHl0aG9uKQpgYGB7cHl0aG9ufQojIFRoaXMgc25pcHBldCBpcyB3cml0dGVuIGluIHB5dGhvbjMKIyBpbXBvcnQgcGFuZGFzIGFzIHBkCiMgaW1wb3J0IG51bXB5IGFzIG5wCiMgZGYgPSBwZC5yZWFkX2NzdignRGF0YVNldC5jc3YnKQojIAojIGZvciBjb2x1bW4gaW4gZGYuY29sdW1uczoKIyAgIHByaW50KGNvbHVtbikKIyAgIHByaW50KGRmW2RmW2NvbHVtbl0uaXNudWxsKCldLmluZGV4LnRvbGlzdCgpKQojIAojIGRmWydGRUFUVVJFXzMnXSA9IGRmWydGRUFUVVJFXzMnXS5zdHIucmVwbGFjZSgnJScsIiIpCiMgZGZbJ0RBVEUnXSA9IHBkLnRvX2RhdGV0aW1lKGRmWydEQVRFJ10sIGZvcm1hdD0iJW0vJWQvJVkiKQojCiMgIyMjIyBXZSBjYW4gdXNlIGEgZGljdGlvbmFyeSB0byBtYXAgdGhlIHN0cmluZyBwYXR0ZXJuIHRvIG5hbgojICMjIyMgY2xlYW5pbmdfZGljdCA9IHsnXiMuKic6IG5wLm5hbn0KIyAjIyMjIGRmWydGRUFUVVJFXzMnXSA9IGRmWydGRUFUVVJFXzMnXS5yZXBsYWNlKGNsZWFuaW5nX2RpY3QsIHJlZ2V4PVRydWUpCiMgIyMjIyBkZlsnQklOQVJZX0ZFQVRVUkUnXSA9IGRmWydCSU5BUllfRkVBVFVSRSddLnJlcGxhY2UoY2xlYW5pbmdfZGljdCwgcmVnZXg9VHJ1ZSkKIyAjIE9SIFVzaW5nIGVycm9ycz0nY29lcmNlJyB3aWxsIHJlcGxhY2UgYWxsIG5vbi1udW1lcmljIHZhbHVlcyB3aXRoIE5hTgojIGRmWydCSU5BUllfRkVBVFVSRSddID0gcGQudG9fbnVtZXJpYyhkZlsnQklOQVJZX0ZFQVRVUkUnXSxlcnJvcnM9J2NvZXJjZScpCiMgZGZbJ0ZFQVRVUkVfMyddID0gcGQudG9fbnVtZXJpYyhkZlsnRkVBVFVSRV8zJ10sZXJyb3JzPSdjb2VyY2UnKQojICMjIyMgRHJvcCBhbGwgbmFuCiMgZGZfcm1uYSA9IGRmLmRyb3BuYSgpCiMgIyMjIyBTYXZlIGZpbGUKIyBkZl9ybW5hLnRvX2NzdignRGF0YVNldF9ybU5BLmNzdicpCmBgYAoKIyBTZWN0aW9uIElJOiBEYXRhIEV4cGxvcmF0aW9uIChSKQoKCmBgYHtyfQpzZXR3ZCgifi9Eb2N1bWVudHMvaW50ZXJ2aWV3X3ByZXAvUXVlc3Rpb24xIikKZGF0YV9pbiA8LSByZWFkLmNzdihmaWxlID0gJ0RhdGFTZXQuY3N2JykKZGF0ZV90cyA8LSBhcy5EYXRlKGRhdGFfaW4kREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKbWFya2V0Y2xvc2VfdHMgPC0gdHMoZGF0YV9pbiRNQVJLRVRDTE9TRSwgc3RhcnQ9ZGF0ZV90c1sxXSkKI3ByaW50KG1hcmtldGNsb3NlX3RzKQpwbG90LnRzKG1hcmtldGNsb3NlX3RzKQpgYGAKYGBge3J9CmxpYnJhcnkoeHRzKQptYXJrZXRjbG9zZV94dHMgPC0geHRzKHg9ZGF0YV9pbiRNQVJLRVRDTE9TRSxvcmRlci5ieSA9IGRhdGVfdHMpCmF1dG9wbG90KG1hcmtldGNsb3NlX3h0cykKYGBgCgpgYGB7cn0KIyBTcGxpdCBtYXJrZXRjbG9zZSBieSB3ZWVrIGFuZCBjYWxjdWxhdGUgYXZlcmFnZXMgcGVyIHdlZWsKd2Vla2x5X21hcmtldGNsb3NlX3h0cyA8LSBzcGxpdChtYXJrZXRjbG9zZV94dHMsIGYgPSAid2Vla3MiKQpwcmludChoZWFkKHdlZWtseV9tYXJrZXRjbG9zZV94dHNbWzFdXSkpCnByaW50KGhlYWQod2Vla2x5X21hcmtldGNsb3NlX3h0c1tbMl1dKSkKCmRhdGVzX3dlZWtseV9lbmRwb2ludHMgPC0geHRzOjplbmRwb2ludHMobWFya2V0Y2xvc2VfeHRzLG9uPSd3ZWVrcycpCnJlYWRpbmdzX3Blcl93ZWVrIDwtIGRhdGVzX3dlZWtseV9lbmRwb2ludHNbLWMoMSldLWRhdGVzX3dlZWtseV9lbmRwb2ludHNbLWMobGVuZ3RoKGRhdGVzX3dlZWtseV9lbmRwb2ludHMpKV0KCiMgcHJpbnQgd2Vla3Mgd2l0aCBsZXNzIHRoYW4gNSB2YWx1ZXMgcGVyIHdlZWsKZm9yICh3ZWVrSUQgaW4gMTpsZW5ndGgod2Vla2x5X21hcmtldGNsb3NlX3h0cykpIHsKICBpZiAobGVuZ3RoKHdlZWtseV9tYXJrZXRjbG9zZV94dHNbW3dlZWtJRF1dWywxXSkgPCA1KSB7CiAgICB3ZWVrX2xlbiA8LSBsZW5ndGgod2Vla2x5X21hcmtldGNsb3NlX3h0c1tbd2Vla0lEXV0pCiAgICB3ZWVrX2VuZHBvaW50X0lEIDwtIGRhdGVzX3dlZWtseV9lbmRwb2ludHNbMSt3ZWVrSURdCiAgICBwcmludChwYXN0ZSgiV2Vla0lEOiIsd2Vla0lELCIsIG51bV9yZWFkaW5nczoiLHdlZWtfbGVuKSkKICAgIHByaW50KHBhc3RlKCJXZWVrX3N0YXJ0OiIsYXMuY2hhcmFjdGVyKGRhdGVfdHNbd2Vla19lbmRwb2ludF9JRC13ZWVrX2xlbisxXSksCiAgICAiLCBXZWVrX2VuZDoiLGFzLmNoYXJhY3RlcihkYXRlX3RzW3dlZWtfZW5kcG9pbnRfSURdKSApICkKICB9Cn0KCmBgYAoKYGBge3J9CndlZWtseV9hdmdfbWFya2V0Y2xvc2UgPC0gbGFwcGx5KFggPSB3ZWVrbHlfbWFya2V0Y2xvc2VfeHRzLCBmdW5jdGlvbihYKXtyZXR1cm4obWVhbihYKSl9KQphbGxfd2Vla3MgPC0gdHModW5saXN0KHdlZWtseV9hdmdfbWFya2V0Y2xvc2UpKQpmaWx0ZXJlZF93ZWVrcyA8LSBhbGxfd2Vla3MKZmlsdGVyZWRfd2Vla3NbcmVhZGluZ3NfcGVyX3dlZWs8NV0gPC0gTmFOCiNmaWx0ZXJlZF93ZWVrcyA8LSB0cyh1bmxpc3Qod2Vla2x5X2F2Z19tYXJrZXRjbG9zZSlbcmVhZGluZ3NfcGVyX3dlZWs9PTVdKQpjbG9zZXMgPC0gY2JpbmQoYWxsX3dlZWtzLCBmaWx0ZXJlZF93ZWVrcykKYXV0b3Bsb3QoY2xvc2VzKQojYXV0b3Bsb3QodHModW5saXN0KHdlZWtseV9hdmdfbWFya2V0Y2xvc2UpKSkKYGBgCgoKSXQgd291bGQgYmUgaW50ZXJlc3RpbmcgdG8gc2VlIGVmZmVjdHMgb2YgaG9saWRheXMvbG9uZyB3ZWVrZW5kcywgYW5udWFsIGV2ZW50cyAoZWcgc3VwZXJib3dsLCBxdWFkIHdpdGNoaW5nKS4gV2Ugd2lsbCBpZ25vcmUgdGhlbSBmb3Igbm93LiBPdGhlciBmYWN0b3JzIGluY2x1ZGUgc2VjdG9yIHNwZWNpZmljIHNlbnRpbWVudCBvciB3aG9sZSBtYXJrZXQgc2VudGltZW50LCB3aGljaCBob3BlZnVsbHkgZGVjb21wb3NpdGlvbiBiZWxvdyB3aWxsIHVuY292ZXIuIAoKTnVtYmVyIG9mIGRhaWx5IHJlYWRpbmdzIGJ5IHllYXI6CgpgYGB7cn0KCnRhYmxlKGZvcm1hdChkYXRlX3RzLCIlWSIpKQpgYGAKCk51bWJlciBvZiB3ZWVrbHkgcmVhZGluZ3MgYnkgeWVhcjoKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90c1tkYXRlc193ZWVrbHlfZW5kcG9pbnRzWy1jKDEpXV0sIiVZIikpCmBgYAoKTnVtYmVyIG9mIHRyYWRpbmcgZGF5cyBwZXIgdHJhZGluZyB3ZWVrIChhdmcpIGJ5IHllYXIKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90cywiJVkiKSkvdGFibGUoZm9ybWF0KGRhdGVfdHNbZGF0ZXNfd2Vla2x5X2VuZHBvaW50c1stYygxKV1dLCIlWSIpKQpgYGAKCk51bWJlciBvZiB0cmFkaW5nIGRheXMgcGVyIG1vbnRoIGJ5IHllYXIKCmBgYHtyfQpkYXRlc19tb250aGx5X2VuZHBvaW50cyA8LSB4dHM6OmVuZHBvaW50cyhtYXJrZXRjbG9zZV94dHMsb249J21vbnRocycpCnRhYmxlKGZvcm1hdChkYXRlX3RzW2RhdGVzX21vbnRobHlfZW5kcG9pbnRzWy1jKDEpXV0sIiVZIikpCnRhYmxlKGZvcm1hdChkYXRlX3RzLCIlWSIpKS90YWJsZShmb3JtYXQoZGF0ZV90c1tkYXRlc19tb250aGx5X2VuZHBvaW50c1stYygxKV1dLCIlWSIpKQpgYGAKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90cywiJW0vJVkiLHN0YXJ0PSgiMDcvMjAwMiIpKSkKYGBgCgpgYGB7cn0KbWVhbih0YWJsZShmb3JtYXQoZGF0ZV90cywiJW0vJVkiKSkpCmBgYAoKCmBgYHtyfQp0YWJsZShmb3JtYXQoZGF0ZV90cywiJVkiKSkKYGBgCgoKTGFzdCByZWFkaW5nIGRhdGUgZm9yIGVhY2ggeWVhcjoKCmBgYHtyfQpkYXRlX3RzW3h0czo6ZW5kcG9pbnRzKG1hcmtldGNsb3NlX3h0cyxvbj0neWVhcnMnKVstYygxKV1dCmBgYAoKWWVhciAyMDAzLDA0LDA1LDA2IHNlYXNvbmFsIHRyZW5kczoKYGBge3J9CndlZWtseV9hdmdfbWFya2V0Y2xvc2VfMjAwMzQ1NiAgPC0gdHMoYXMubnVtZXJpYyh1bmxpc3Qod2Vla2x5X2F2Z19tYXJrZXRjbG9zZSkpWygyNisxKTooMjYrNTMrNTIrNTIrNTIpXSxmcmVxdWVuY3k9NTIpCiNsaWJyYXJ5KGZvcmVjYXN0KQpub190cmFuc2Zvcm1fMjAwMzQ1NiA8LSBzdGwobG9nKHdlZWtseV9hdmdfbWFya2V0Y2xvc2VfMjAwMzQ1NiksIHMud2luZG93PTUzKQojbm9fdHJhbnNmb3JtXzIwMDM0NTYgPC0gbXN0bCh3ZWVrbHlfYXZnX21hcmtldGNsb3NlXzIwMDM0NTYpCmF1dG9wbG90KG5vX3RyYW5zZm9ybV8yMDAzNDU2KQojVGltZVNlcmllc1dlZWtseURlY29tcG9zZWQ8LXN0bCh0c19kYXRhICwgcy53aW5kb3c9InBlcmlvZGljIikKYGBgCgpTZWFzb25hbCB0cmVuZHM6CgpgYGB7cn0KbGlicmFyeShmb3JlY2FzdCkKbm9fdHJhbnNmb3JtXzIwMDM0NTYgJT4lIHNlYXNvbmFsKCkgJT4lIGdnc3Vic2VyaWVzcGxvdCgpCmBgYAoKYGBge3J9Cm5vX3RyYW5zZm9ybV8yMDAzNDU2ICU+JSByZW1haW5kZXIoKSAlPiUgYXV0b3Bsb3QoKQpgYGAKCgpgYGB7cn0Kcm0obGlzdD1scygpKQpgYGAKCmBgYHtyfQpzZXR3ZCgifi9Eb2N1bWVudHMvaW50ZXJ2aWV3X3ByZXAvUXVlc3Rpb24xIikKZGF0YV9pbiA8LSByZWFkLmNzdihmaWxlID0gJ0RhdGFTZXQuY3N2JykKZGF0ZV90cyA8LSBhcy5EYXRlKGRhdGFfaW4kREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKbWFya2V0Y2xvc2VfdHMgPC0gdHMoZGF0YV9pbiRNQVJLRVRDTE9TRSwgc3RhcnQ9ZGF0ZV90c1sxXSxmcmVxdWVuY3kgPSAyNTEuNjI1KQpwbG90LnRzKG1hcmtldGNsb3NlX3RzKQpgYGAKCgoKR2l2ZW4gdGhlIGlycmVndWxhciBuYXR1cmUgb2YgdGhlIHRpbWUgc2VyaWVzIHdlIHVzZSB4dHMgbW9kZWxpbmcgYW5kIGVzdGltYXRlIGRhaWx5IHJldHVybnMgJQoKYGBge3J9CgpsaWJyYXJ5KHh0cykKbWFya2V0Y2xvc2VfeHRzIDwtIHh0cyh4PWRhdGFfaW4kTUFSS0VUQ0xPU0Usb3JkZXIuYnkgPSBkYXRlX3RzKQptYXJrZXRjbG9zZV9yZXQgPC0gMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpKQojbWFya2V0Y2xvc2VfcmV0IDwtIGFzLnpvbyhtYXJrZXRjbG9zZV9yZXRbLWMoMSldKQpwbG90LnpvbyhtYXJrZXRjbG9zZV9yZXQsIHlsYWIgPSAiRGFpbHkgcmV0dXJucyAoJSkiLCBtYWluID0gIlBlcmNlbnRhZ2UgZGFpbHkgcmV0dXJucyIpCiMgbG93ZXNzIGZpdCB3aXRoIHRoZSBmID0gMS9hdmVyYWdlICMgdHJhZGluZyBkYXlzIGluIGEgbW9udGgKIyBDYWxjdWxhdGlvbiBmb3IgMjAuOCBmb2xsb3cgYmVsb3cKbGluZXMoaW5kZXgobWFya2V0Y2xvc2VfcmV0KSwgbG93ZXNzKG1hcmtldGNsb3NlX3JldFssMV0sIGYgPSAxLzIwLjgpJHksIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQpgYGAKCmBgYHtyfQojIFZvbGF0aWxpdHkgYXMgZnVuY3Rpb24gb2YgbW9udGgKcGxvdChqaXR0ZXIoYXMubnVtZXJpYyhmb3JtYXQoaW5kZXgobWFya2V0Y2xvc2VfcmV0KSwiJW0iKSksIGFtb3VudCA9IDEvMyksIG1hcmtldGNsb3NlX3JldCwgcGNoID0gMjAsIHlsYWIgPSAiRGFpbHkgcGVyY2VudCByZXR1cm5zIiwgeGxhYiA9ICJNb250aCIsIGJ0eSA9ICJsIikKYGBgCgpgYGB7cn0KYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3RvcihxdWFydGVycyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsYWJlbHM9YygiUTMiLCJRNCIsIlExIiwiUTIiKSksIHhsYWIgPSAiUXVhcnRlciIsIHBjaCA9IDIwLCBjb2wgPSByZ2IoMCwgMCwgMCwgMC40KSwgeWxhYiA9ICJEYWlseSBwZXJjZW50IHJldHVybnMiLCBidHkgPSAibCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgpgYGB7cn0KYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcihtb250aHMoaW5kZXgobWFya2V0Y2xvc2VfcmV0KSxhYmJyZXZpYXRlID0gVFJVRSksICAgICBsZXZlbHMgPSBjKCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiLCJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iKSksIHhsYWIgPSAiTW9udGgiLCBwY2ggPSAyMCwgY29sID0gcmdiKDAsIDAsIDAsIDAuNCksIHlsYWIgPSAiRGFpbHkgcGVyY2VudCByZXR1cm5zIiwgYnR5ID0gImwiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKCmBgYHtyfQpib3hwbG90KGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXQpIH4gZmFjdG9yKHdlZWtkYXlzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpLAogICAgICAgIGxldmVscyA9IGMoIk1vbiIsIlR1ZSIsIldlZCIsIlRodSIsIkZyaSIpKSAseGxhYiA9ICJEYXkgb2YgdGhlIHdlZWsiLCBwY2ggPSAyMCwgY29sID0gcmdiKDAsIDAsIDAsIDAuNCksIHlsYWIgPSAiRGFpbHkgcGVyY2VudCByZXR1cm5zIiwgYnR5ID0gImwiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKCmBgYHtyfQpteXBsb3QgPC0gYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSkqZmFjdG9yKG1vbnRocyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiLCJKYW4iLCJGZWIiLCJNYXIiLCJBcHIiLCJNYXkiLCJKdW4iKSksCnhsYWIgPSAiRGF5IG9mIHRoZSB3ZWVrIGluIGdpdmVuIG1vbnRoIiwgcGNoID0gMjAsIGNvbCA9IHJnYigwLCAwLCAwLCAwLjQpLCB5bGFiID0gIkRhaWx5IHBlcmNlbnQgcmV0dXJucyIsIGJ0eSA9ICJsIikKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpteXBsb3QgPC0gYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSkqZmFjdG9yKHF1YXJ0ZXJzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpLGxldmVscyA9IGMoIlEzIiwiUTQiLCJRMSIsIlEyIikpLAp4bGFiID0gIkRheSBvZiB0aGUgd2VlayBpbiBnaXZlbiB5ZWFyIiwgcGNoID0gMjAsIGNvbCA9IHJnYigwLCAwLCAwLCAwLjQpLCB5bGFiID0gIkRhaWx5IHBlcmNlbnQgcmV0dXJucyIsIGJ0eSA9ICJsIikKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpteXBsb3QgPC0gYm94cGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSB+IGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSkqZmFjdG9yKGZvcm1hdChpbmRleChtYXJrZXRjbG9zZV9yZXQpLCIlWSIpLGxldmVscyA9IGMoIjIwMDIiLCIyMDAzIiwiMjAwNCIsIjIwMDUiLCIyMDA2IiwiMjAwNyIpKSwKeGxhYiA9ICJEYXkgb2YgdGhlIHdlZWsgaW4gZ2l2ZW4geWVhciIsIHBjaCA9IDIwLCBjb2wgPSByZ2IoMCwgMCwgMCwgMC40KSwgeWxhYiA9ICJEYWlseSBwZXJjZW50IHJldHVybnMiLCBidHkgPSAibCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgpNb25kYXkgcmV0dXJucyBhcyBmdW5jdGlvbiBvZiByZXR1cm4gb24gZnJpZGF5IHJldHVybiBpbiBwcmV2IHdlZWsgYnkgeWVhcgoKYGBge3J9CiNoZWFkKHdoaWNoKGZhY3Rvcih3ZWVrZGF5cyhpbmRleChtYXJrZXRjbG9zZV9yZXQpLGFiYnJldmlhdGUgPSBUUlVFKSxsZXZlbHMgPSBjKCJNb24iLCJUdWUiLCJXZWQiLCJUaHUiLCJGcmkiKSk9PSJNb24iKSkKI2hlYWQod2hpY2goZmFjdG9yKHdlZWtkYXlzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpLGxldmVscyA9IGMoIk1vbiIsIlR1ZSIsIldlZCIsIlRodSIsIkZyaSIpKT09IkZyaSIpKQoKCm1vbmRheV9pbmRpY2VzIDwtIHdoaWNoKHdlZWtkYXlzKGluZGV4KG1hcmtldGNsb3NlX3JldCksYWJicmV2aWF0ZSA9IFRSVUUpPT0iTW9uIikKbW9uZGF5X2luZGljZXMgPC0gbW9uZGF5X2luZGljZXNbLWMoMSldCnBsb3QoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFttb25kYXlfaW5kaWNlc10pfmFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzLTEpXSksCiAgICAgeGxhYj0iUHJldiBkYXkgY2hhbmdlIix5bGFiPSJNb25kYXkgY2hhbmdlIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbbW9uZGF5X2luZGljZXNdKX5hcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0xKV0pLGY9MS8yKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgoKYGBge3J9CgpwbG90LnRzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzKV0pLHlsaW09YygtMS41LDEuNSksCiAgICAgeGxhYj0iRGF0ZSIseWxhYiA9ICIlIENoYW5nZSIpCiNsaW5lcyhhcy52ZWN0b3IoMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpWyhtb25kYXlfaW5kaWNlcy0xKV0pKSxjb2w9MikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzLTEpXSksZj0xLzIpLGNvbD0iZ3JlZW4iLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMpXSksZj0xLzIpLGNvbD0icmVkIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0yKV0pLGY9MS8yKSxjb2w9ImJsdWUiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKI2xpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0zKV0pLGY9MS8yKSxjb2w9InBpbmsiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzKV0pKSxmPTEvMiksY29sPSJibGFjayIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMSldKSksZj0xLzIpLGNvbD0ib3JhbmdlIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMildKSksZj0xLzIpLGNvbD0ieWVsbG93Iix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhzaWduKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0KVttb25kYXlfaW5kaWNlc10pKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYApgYGB7cn0KcGxvdC50cyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcyldKSx5bGltPWMoLTEuNSwxLjUpLAogICAgIHhsYWI9IkRhdGUiLHlsYWIgPSAiJSBDaGFuZ2UiKQojbGluZXMoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMSldKSksY29sPTIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0xKV0pLGY9MS80KSxjb2w9ImdyZWVuIix0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzKV0pLGY9MS80KSxjb2w9InJlZCIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMtMildKSxmPTEvNCksY29sPSJibHVlIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMtMyldKSxmPTEvMiksY29sPSJwaW5rIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IoMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpWyhtb25kYXlfaW5kaWNlcyldKSksZj0xLzQpLGNvbD0iYmxhY2siLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzLTEpXSkpLGY9MS80KSxjb2w9Im9yYW5nZSIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQojbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzLTIpXSkpLGY9MS8yKSxjb2w9InllbGxvdyIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQojbGluZXMoc2lnbihhcy5udW1lcmljKG1hcmtldGNsb3NlX3JldClbbW9uZGF5X2luZGljZXNdKSkKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpwbG90LnRzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzKV0pLHlsaW09YygtMS41LDEuNSksCiAgICAgeGxhYj0iRGF0ZSIseWxhYiA9ICIlIENoYW5nZSIpCiNsaW5lcyhhcy52ZWN0b3IoMTAwKmRpZmYueHRzKGxvZyhtYXJrZXRjbG9zZV94dHMpWyhtb25kYXlfaW5kaWNlcy0xKV0pKSxjb2w9MikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzLTEpXSksZj0xLzgpLGNvbD0iZ3JlZW4iLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXMpXSksZj0xLzgpLGNvbD0icmVkIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0yKV0pLGY9MS84KSxjb2w9ImJsdWUiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKI2xpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlcy0zKV0pLGY9MS8yKSxjb2w9InBpbmsiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcigxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0cylbKG1vbmRheV9pbmRpY2VzKV0pKSxmPTEvOCksY29sPSJibGFjayIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMSldKSksZj0xLzgpLGNvbD0ib3JhbmdlIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhsb3dlc3MoYXMudmVjdG9yKDEwMCpkaWZmLnh0cyhsb2cobWFya2V0Y2xvc2VfeHRzKVsobW9uZGF5X2luZGljZXMtMildKSksZj0xLzIpLGNvbD0ieWVsbG93Iix5bGltPWMoLTEsMSksdHlwZT0ibyIpCiNsaW5lcyhzaWduKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0KVttb25kYXlfaW5kaWNlc10pKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKCkV2ZW4gd2l0aCBmPTEvOCBpdCBzZWVtcyBNb25kYXkgZGFpbHkgcmV0dXJucyB3aWxsIGZvbGxvdyBzdHJpY3RseSBkb3dud2FyZCB0cmVuZAoKYGBge3J9CiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTEpLDExNzMpXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTIpLDExNzIpXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksZj0xLzgpJHksMTUpCiN0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzKSldKSxmPTEvOCkkeSwxNSkKCnBsb3QodGFpbChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0xKSwxMTczKV0pLGY9MS84KSR5LDUwKSx5bGltPWMoLTEsMSksdHlwZT0nbCcsbWFpbj0idHJlbmQgNTAgZGF5IHJldHVybnMgYnkgZGF5IG9mIHRoZSB3ZWVrIix5bGFiPSIlIENoYW5nZSIseGxhYj0iZGF5IikKbGluZXModGFpbChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0yKSwxMTcyKV0pLGY9MS84KSR5LDUwKSxjb2w9ImJsdWUiKQpsaW5lcyh0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksZj0xLzgpJHksNTApLGNvbD0iZ3JlZW4iKQpsaW5lcyh0YWlsKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksZj0xLzgpJHksNTApLGNvbD0ib3JhbmdlIikKbGluZXModGFpbChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcykpXSksZj0xLzgpJHksNTApLGNvbD0icmVkIikKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCmBgYHtyfQpwbG90KGMocmVwKDAsNTApLHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXQpLDUwKSkseWxpbT1jKC0xLDEpLHR5cGU9J2wnLG1haW49IjUwICYgMjAwIGRheSBNQSBvZiBkYWlseSByZXR1cm5zIix5bGFiPSIlIENoYW5nZSIseGxhYj0iZGF5Iixjb2w9InJlZCIpCmxpbmVzKGMocmVwKDAsMjAwKSxyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0KSwyMDApKSxjb2w9ImdyZWVuIikKI2xpbmVzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXQpKQpsaW5lcyhsb3dlc3MobWFya2V0Y2xvc2VfcmV0WywxXSwgZiA9IDEvMjAuOCkkeSwgbHdkID0gMikKI2xpbmVzKHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbYygobW9uZGF5X2luZGljZXMtMiksMTE3MildKSw1MCksY29sPSJibHVlIikKI2xpbmVzKHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbYygobW9uZGF5X2luZGljZXMtMyksMTE3MSldKSw1MCksY29sPSJncmVlbiIpCiNsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksNTApLGNvbD0ib3JhbmdlIikKI2xpbmVzKHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbbW9uZGF5X2luZGljZXNdKSw1MCksY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKYGBge3J9CnBsb3Qocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0xKSwxMTczKV0pLDUwKSx5bGltPWMoLS4yNSwuMjUpLHR5cGU9J2wnLG1haW49IjUwIHdlZWsgTUEgb2Ygd2Vla2x5IHJldHVybnMgYnkgZGF5IG9mIHRoZSB3ZWVrIix5bGFiPSIlIENoYW5nZSIseGxhYj0iZGF5IikKbGluZXMocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy0yKSwxMTcyKV0pLDUwKSxjb2w9ImJsdWUiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksNTApLGNvbD0iZ3JlZW4iKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTQpLDExNzApXSksNTApLGNvbD0ib3JhbmdlIikKbGluZXMocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFttb25kYXlfaW5kaWNlc10pLDUwKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCmBgYHtyfQpwbG90KHJvbGxtZWFuKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbYygobW9uZGF5X2luZGljZXMtMSksMTE3MyldKSwyMDApLHlsaW09YygtLjI1LC4yNSksdHlwZT0nbCcsbWFpbj0iMjAwIHdlZWsgTUEgcmV0dXJucyBieSBkYXkgb2YgdGhlIHdlZWsiLHlsYWI9IiUgQ2hhbmdlIix4bGFiPSJkYXkiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTIpLDExNzIpXSksMjAwKSxjb2w9ImJsdWUiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W2MoKG1vbmRheV9pbmRpY2VzLTMpLDExNzEpXSksMjAwKSxjb2w9ImdyZWVuIikKbGluZXMocm9sbG1lYW4oYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFtjKChtb25kYXlfaW5kaWNlcy00KSwxMTcwKV0pLDIwMCksY29sPSJvcmFuZ2UiKQpsaW5lcyhyb2xsbWVhbihhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W21vbmRheV9pbmRpY2VzXSksMjAwKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgptb25kYXkgcmV0dXJuIHdydCBwcmV2IG1vbmRheQoKYGBge3J9CmluZGljZXNfMjAwNyA8LSB3aGljaChhcy5udW1lcmljKGZvcm1hdChpbmRleChtYXJrZXRjbG9zZV9yZXQpLCIlWSIpKSAlaW4lIGMoMjAwNiwyMDA3KSkKbW9uZGF5X2luZGljZXNfMjAwNyA8LSBtb25kYXlfaW5kaWNlc1ttb25kYXlfaW5kaWNlcyAlaW4lIGluZGljZXNfMjAwN10KcGxvdChhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0W21vbmRheV9pbmRpY2VzXzIwMDddKX5hcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlc18yMDA3LTEpXSksCiAgICAgeGxhYj0iUHJldiBkYXkgY2hhbmdlIDIwMDYtMjAwNyIseWxhYj0iTW9uZGF5IGNoYW5nZSAyMDA2LTIwMDciKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFttb25kYXlfaW5kaWNlc18yMDA3XSl+YXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXNfMjAwNy0xKV0pLGY9MS8yKSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgoKYGBge3J9CiNwbG90Lnpvbyh4dHMoc2lnbihhcy5udW1lcmljKG1hcmtldGNsb3NlX3JldClbbW9uZGF5X2luZGljZXNfMjAwN10pKnNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpWyhtb25kYXlfaW5kaWNlc18yMDA3LTEpXSksb3JkZXIuYnkgPSBpbmRleChtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSxtYWluPSAiU2lnbiBvZiBNb25kYXkgY2hhbmdlIHRpbWVzIHNpZ24gb2YgcHJldiBkYXkiKQojbGluZXMoeHRzKHNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSxvcmRlci5ieSA9IGluZGV4KG1hcmtldGNsb3NlX3JldClbbW9uZGF5X2luZGljZXNfMjAwN10pLGNvbD0icmVkIikKI2xpbmVzKHNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSkKcGxvdChsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXNfMjAwNy0xKV0pLGY9MS8yKSxjb2w9ImdyZWVuIix5bGltPWMoLTEsMSksCiAgICAgeGxhYj0iRGF0ZSIseWxhYiA9ICIlIENoYW5nZSIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0Wyhtb25kYXlfaW5kaWNlc18yMDA3KV0pLGY9MS8yKSxjb2w9InJlZCIseWxpbT1jKC0xLDEpLHR5cGU9Im8iKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldFsobW9uZGF5X2luZGljZXNfMjAwNy0yKV0pLGY9MS8yKSxjb2w9ImJsdWUiLHlsaW09YygtMSwxKSx0eXBlPSJvIikKbGluZXMobG93ZXNzKGFzLnZlY3RvcihtYXJrZXRjbG9zZV9yZXRbKG1vbmRheV9pbmRpY2VzXzIwMDctMyldKSxmPTEvMiksY29sPSJwaW5rIix5bGltPWMoLTEsMSksdHlwZT0ibyIpCmxpbmVzKHNpZ24oYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXQpW21vbmRheV9pbmRpY2VzXzIwMDddKSkKZ3JpZChjb2w9MSxsd2Q9MS41KQpgYGAKCiMjIFBsb3Qgb2YgZmVhdHVyZXMKCgoKYGBge3J9CnNldHdkKCJ+L0RvY3VtZW50cy9pbnRlcnZpZXdfcHJlcC9RdWVzdGlvbjEiKQpkYXRhX2luX2NsZWFuZWQgPC0gcmVhZC5jc3YoZmlsZSA9ICdEYXRhU2V0X3JtTkEuY3N2JykKZGF0ZV90c19jbGVhbmVkIDwtIGFzLkRhdGUoZGF0YV9pbl9jbGVhbmVkJERBVEUsIGZvcm1hdCA9ICIlWS0lbS0lZCIpCm1hcmtldGNsb3NlX3RzX2NsZWFuZWQgPC0gdHMoZGF0YV9pbl9jbGVhbmVkJE1BUktFVENMT1NFLCBzdGFydD1kYXRlX3RzX2NsZWFuZWRbMV0sZnJlcXVlbmN5ID0gMjUxLjYyNSkKcGxvdC50cyhtYXJrZXRjbG9zZV90c19jbGVhbmVkKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHh0cykKbWFya2V0Y2xvc2VfeHRzX2NsZWFuZWQgPC0geHRzKHg9ZGF0YV9pbl9jbGVhbmVkJE1BUktFVENMT1NFLG9yZGVyLmJ5ID0gZGF0ZV90c19jbGVhbmVkKQptYXJrZXRjbG9zZV9yZXRfY2xlYW5lZCA8LSAxMDAqZGlmZi54dHMobG9nKG1hcmtldGNsb3NlX3h0c19jbGVhbmVkKSkKIyMgbWFya2V0Y2xvc2VfcmV0X2NsZWFuZWQgPC0gbWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRbLWMoMSldCiNtYXJrZXRjbG9zZV9yZXQgPC0gYXMuem9vKG1hcmtldGNsb3NlX3JldFstYygxKV0pCnBsb3Quem9vKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkLCB5bGFiID0gIkRhaWx5IHJldHVybnMgKCUpIiwgbWFpbiA9ICJQZXJjZW50YWdlIGRhaWx5IHJldHVybnMiKQojIGxvd2VzcyBmaXQgd2l0aCB0aGUgZiA9IDEvYXZlcmFnZSAjIHRyYWRpbmcgZGF5cyBpbiBhIG1vbnRoCiMgQ2FsY3VsYXRpb24gZm9yIDIwLjggZm9sbG93IGJlbG93CmxpbmVzKGluZGV4KG1hcmtldGNsb3NlX3JldF9jbGVhbmVkKSwgbG93ZXNzKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkWywxXSwgZiA9IDEvMjAuOCkkeSwgY29sID0gInJlZCIsIGx3ZCA9IDIpCmZlYXR1cmUxX3h0cyA8LSB4dHMoeD1kYXRhX2luX2NsZWFuZWQkRkVBVFVSRV8xLG9yZGVyLmJ5ID0gZGF0ZV90c19jbGVhbmVkKQpmZWF0dXJlMV9kaWZmX3h0cyA8LSBkaWZmLnh0cygoZmVhdHVyZTFfeHRzKSkKIyMgZmVhdHVyZTFfZGlmZl94dHMgPC0gZmVhdHVyZTFfZGlmZl94dHNbLWMoMSldCmxpbmVzKGluZGV4KGZlYXR1cmUxX2RpZmZfeHRzKSwgbG93ZXNzKGZlYXR1cmUxX2RpZmZfeHRzWywxXSwgZiA9IDEvMjAuOCkkeSwgY29sID0gImJsdWUiLCBsd2QgPSAyKQpmZWF0dXJlMl94dHMgPC0geHRzKHg9ZGF0YV9pbl9jbGVhbmVkJEZFQVRVUkVfMixvcmRlci5ieSA9IGRhdGVfdHNfY2xlYW5lZCkKZmVhdHVyZTJfZGlmZl94dHMgPC0gZGlmZi54dHMoKGZlYXR1cmUyX3h0cykpCmxpbmVzKGluZGV4KGZlYXR1cmUyX2RpZmZfeHRzKSwgbG93ZXNzKGZlYXR1cmUyX2RpZmZfeHRzWywxXSwgZiA9IDEvMjAuOCkkeSwgY29sID0gImdyZWVuIiwgbHdkID0gMikKIyMgZmVhdHVyZTJfZGlmZl94dHMgPC0gZmVhdHVyZTJfZGlmZl94dHNbLWMoMSldCgpmZWF0dXJlM194dHMgPC0geHRzKHg9ZGF0YV9pbl9jbGVhbmVkJEZFQVRVUkVfMyxvcmRlci5ieSA9IGRhdGVfdHNfY2xlYW5lZCkKZmVhdHVyZTNfc2NhbGVkX3h0cyA8LSAoKGZlYXR1cmUzX3h0cykpLzEwMApsaW5lcyhpbmRleChmZWF0dXJlM19zY2FsZWRfeHRzKSwgbG93ZXNzKGZlYXR1cmUzX3NjYWxlZF94dHNbLDFdLCBmID0gMS8yMC44KSR5LCBjb2wgPSAib3JhbmdlIiwgbHdkID0gMikKIyMgZmVhdHVyZTNfc2NhbGVkX3h0cyA8LSAgZmVhdHVyZTNfc2NhbGVkX3h0c1stYygxKV0KCmJpbmFyeWZfeHRzIDwtIHh0cyh4PWRhdGFfaW5fY2xlYW5lZCRCSU5BUllfRkVBVFVSRSxvcmRlci5ieSA9IGRhdGVfdHNfY2xlYW5lZCkKIyMgYmluYXJ5Zl94dHMgPC0gYmluYXJ5Zl94dHNbLWMoMSldCmxpbmVzKGluZGV4KGJpbmFyeWZfeHRzKSxiaW5hcnlmX3h0cywgY29sID0gInllbGxvdyIsIGx3ZCA9IDIpCmBgYAoKIyMgQ3Jvc3MgY29yciBiZXR3ZWVuIHRpbWUgc2VyaWVycwpgYGB7cn0KdGl0bGVfbG9ncmV0IDwtICJEYWlseSBsb2cgcmV0dXJucyAoJSkiCnBhcihtZnJvdyA9IGMoMiwgMikpClRTQTo6YWNmKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gdGl0bGVfbG9ncmV0KQpwYWNmKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkLCBuYS5hY3Rpb24gPSBuYS5wYXNzLGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKCgojIChQKUFDRiBvZiBhYnNvbHV0ZSB2YWx1ZSBvZiBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQp0aXRsZV9hYnNsb2dyZXQgPC0gIkRhaWx5IGFicyBsb2cgcmV0dXJucyAoJSkiClRTQTo6YWNmKGFicyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZCksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSB0aXRsZV9hYnNsb2dyZXQpCnBhY2YoYWJzKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9ICIiKQojIChQKUFDRiBvZiBzcXVhcmVkIGRhaWx5IHJldHVybnMKI3BhcihtZnJvdyA9IGMoMSwgMikpCiNUU0E6OmFjZihJKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkXjIpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBtYWluID0gdGl0bGVfc3ApCiNwYWNmKEkobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWReMiksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIG1haW4gPSB0aXRsZV9zcCkKCmBgYAoKYGBge3J9CnRpdGxlX2xvZ3JldCA8LSAiRmVhdHVyZTEgbG9nIHJldHVybnMiCnBhcihtZnJvdyA9IGMoMiwgMikpClRTQTo6YWNmKGZlYXR1cmUxX2RpZmZfeHRzLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gdGl0bGVfbG9ncmV0KQpwYWNmKGZlYXR1cmUxX2RpZmZfeHRzLCBuYS5hY3Rpb24gPSBuYS5wYXNzLGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKCgojIChQKUFDRiBvZiBhYnNvbHV0ZSB2YWx1ZSBvZiBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQp0aXRsZV9hYnNsb2dyZXQgPC0gIkZlYXR1cmUxIGFicyBsb2cgcmV0dXJucyIKVFNBOjphY2YoYWJzKGZlYXR1cmUxX2RpZmZfeHRzKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2Fic2xvZ3JldCkKcGFjZihhYnMoZmVhdHVyZTFfZGlmZl94dHMpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gIiIpCmBgYAoKCmBgYHtyfQp0aXRsZV9sb2dyZXQgPC0gIkZlYXR1cmUyIGxvZyByZXR1cm5zIgpwYXIobWZyb3cgPSBjKDIsIDIpKQpUU0E6OmFjZihmZWF0dXJlMl9kaWZmX3h0cywgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2xvZ3JldCkKcGFjZihmZWF0dXJlMl9kaWZmX3h0cywgbmEuYWN0aW9uID0gbmEucGFzcyxsYWcubWF4ID0gMTAwLCBtYWluID0gIiIpCgoKIyAoUClBQ0Ygb2YgYWJzb2x1dGUgdmFsdWUgb2YgZGFpbHkgcmV0dXJucwojcGFyKG1mcm93ID0gYygxLCAyKSkKdGl0bGVfYWJzbG9ncmV0IDwtICJGZWF0dXJlMiBhYnMgbG9nIHJldHVybnMiClRTQTo6YWNmKGFicyhmZWF0dXJlMl9kaWZmX3h0cyksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSB0aXRsZV9hYnNsb2dyZXQpCnBhY2YoYWJzKGZlYXR1cmUyX2RpZmZfeHRzKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9ICIiKQpgYGAKCmBgYHtyfQp0aXRsZV9sb2dyZXQgPC0gIkZlYXR1cmUzIGxvZyByZXR1cm5zIgpwYXIobWZyb3cgPSBjKDIsIDIpKQpUU0E6OmFjZihmZWF0dXJlM19zY2FsZWRfeHRzLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gdGl0bGVfbG9ncmV0KQpwYWNmKGZlYXR1cmUzX3NjYWxlZF94dHMsIG5hLmFjdGlvbiA9IG5hLnBhc3MsbGFnLm1heCA9IDEwMCwgbWFpbiA9ICIiKQoKCiMgKFApQUNGIG9mIGFic29sdXRlIHZhbHVlIG9mIGRhaWx5IHJldHVybnMKI3BhcihtZnJvdyA9IGMoMSwgMikpCnRpdGxlX2Fic2xvZ3JldCA8LSAiRmVhdHVyZTMgYWJzIGxvZyByZXR1cm5zIgpUU0E6OmFjZihhYnMoZmVhdHVyZTNfc2NhbGVkX3h0cyksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSB0aXRsZV9hYnNsb2dyZXQpCnBhY2YoYWJzKGZlYXR1cmUzX3NjYWxlZF94dHMpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBsYWcubWF4ID0gMTAwLCBtYWluID0gIiIpCmBgYAoKCiMjIFBsb3QgcmV0IHZzIGYxIGYyIGYzCmBgYHtyfQpwYXIobWZyb3cgPWMoMSwzKSApCgptYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF8gPC0gbWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRbLWMoMSldCmZlYXR1cmUxX2RpZmZfeHRzXyA8LSBmZWF0dXJlMV9kaWZmX3h0c1stYygxKV0KcGxvdChhcy5udW1lcmljKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkXykgfiBhcy5udW1lcmljKGZlYXR1cmUxX2RpZmZfeHRzXykgLHhsYWI9IkZlYXR1cmUxIiwKICAgICB5bGFiPSJEYWlseSAlIGxvZyByZXR1cm5zIikKbGluZXMobG93ZXNzKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRfKSB+IGFzLm51bWVyaWMoZmVhdHVyZTFfZGlmZl94dHNfKSwgZj0wLjEpLAogICAgICBjb2w9InJlZCIpCgpmZWF0dXJlMl9kaWZmX3h0c18gPC0gZmVhdHVyZTJfZGlmZl94dHNbLWMoMSldCnBsb3QoYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZCkgfiBhcy5udW1lcmljKGZlYXR1cmUyX2RpZmZfeHRzKSAseGxhYj0iRmVhdHVyZTIiLAogICAgIHlsYWI9IkRhaWx5ICUgbG9nIHJldHVybnMiKQpsaW5lcyhsb3dlc3MoYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF8pIH4gYXMubnVtZXJpYyhmZWF0dXJlMl9kaWZmX3h0c18pLCBmPTAuMSksCiAgICAgIGNvbD0icmVkIikKCmZlYXR1cmUzX3NjYWxlZF94dHNfIDwtIGZlYXR1cmUzX3NjYWxlZF94dHNbLWMoMSldCnBsb3QoYXMubnVtZXJpYyhtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF8pIH4gYXMubnVtZXJpYyhmZWF0dXJlM19zY2FsZWRfeHRzXykgLHhsYWI9IkZlYXR1cmUzIiwKICAgICB5bGFiPSJEYWlseSAlIGxvZyByZXR1cm5zIikKbGluZXMobG93ZXNzKGFzLm51bWVyaWMobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRfKSB+IGFzLm51bWVyaWMoZmVhdHVyZTNfc2NhbGVkX3h0c18pLCBmPTAuMSksCiAgICAgIGNvbD0icmVkIikKCmBgYAoKCmBgYHtyfQpzZWxfZml0IDwtIGF1dG8uYXJpbWEobWFya2V0Y2xvc2VfcmV0X2NsZWFuZWRfLCB4cmVnID0gY2JpbmQoZmVhdHVyZTJfZGlmZl94dHNfICAsZmVhdHVyZTNfc2NhbGVkX3h0c18pKQpgYGAKCmBgYHtyfQpwbG90KHJlc2lkKHNlbF9maXQpKQpgYGAKCgpgYGB7cn0KQm94LnRlc3QocmVzaWQoc2VsX2ZpdCksIGxhZz0xMDAsIHR5cGU9IkxqdW5nIikKYGBgCmBgYHtyfQpxcW5vcm0ocmVzaWQoc2VsX2ZpdCwgdHlwZT0iaW5ub3ZhdGlvbiIpKQpgYGAKYGBge3J9Cm1hcmtldGNsb3NlX3RzIDwtIHRzKGFzLm51bWVyaWMoZGF0YV9pbl9jbGVhbmVkJE1BUktFVENMT1NFKSwgc3RhcnQ9ZGF0ZV90c19jbGVhbmVkWzFdLGZyZXF1ZW5jeSA9IDI1MS42MjUpCm1hcmtldGNsb3NlX3RzX2RlY29tcCA8LSBzdGwobG9nKG1hcmtldGNsb3NlX3RzKSwgcy53aW5kb3c9MjEsIHJvYnVzdD1UUlVFKQojbm9fdHJhbnNmb3JtXzIwMDM0NTYgPC0gbXN0bCh3ZWVrbHlfYXZnX21hcmtldGNsb3NlXzIwMDM0NTYpCmZvcmVjYXN0OjphdXRvcGxvdChtYXJrZXRjbG9zZV90c19kZWNvbXApCiNUaW1lU2VyaWVzV2Vla2x5RGVjb21wb3NlZDwtc3RsKHRzX2RhdGEgLCBzLndpbmRvdz0icGVyaW9kaWMiKQpgYGAKCmBgYHtyfQpmZWF0dXJlMV90cyA8LSB0cyhhcy5udW1lcmljKGZlYXR1cmUxX3h0cyksIHN0YXJ0PWRhdGVfdHNbMV0sZnJlcXVlbmN5ID0gMjUxLjYyNSkKZmVhdHVyZTFfdHNfZGVjb21wIDwtIHN0bChsb2coZmVhdHVyZTFfdHMpLCBzLndpbmRvdz0icGVyaW9kaWMiLCByb2J1c3Q9VFJVRSkKI25vX3RyYW5zZm9ybV8yMDAzNDU2IDwtIG1zdGwod2Vla2x5X2F2Z19tYXJrZXRjbG9zZV8yMDAzNDU2KQphdXRvcGxvdChmZWF0dXJlMV90c19kZWNvbXApCmBgYAoKYGBge3J9CmZlYXR1cmUyX3RzIDwtIHRzKGFzLm51bWVyaWMoZmVhdHVyZTJfeHRzKSwgc3RhcnQ9ZGF0ZV90c1sxXSxmcmVxdWVuY3kgPSAyNTEuNjI1KQpmZWF0dXJlMl90c19kZWNvbXAgPC0gc3RsKGxvZyhmZWF0dXJlMl90cyksIHMud2luZG93PSJwZXJpb2RpYyIsIHJvYnVzdD1UUlVFKQojbm9fdHJhbnNmb3JtXzIwMDM0NTYgPC0gbXN0bCh3ZWVrbHlfYXZnX21hcmtldGNsb3NlXzIwMDM0NTYpCmF1dG9wbG90KGZlYXR1cmUyX3RzX2RlY29tcCkKYGBgCgpgYGB7cn0KI21vbnRocGxvdChtYXJrZXRjbG9zZV90c19kZWNvbXAsY2hvaWNlPSJzZWFzb25hbCIpCiNwbG90KHNlYXNvbmFsKGZlYXR1cmUxX3RzX2RlY29tcCkpCiNsaW5lcyhzZWFzb25hbChtYXJrZXRjbG9zZV90c19kZWNvbXApLGNvbD0yKQpwbG90KGZvcmVjYXN0OjpyZW1haW5kZXIobWFya2V0Y2xvc2VfdHNfZGVjb21wKSkKZmVhdHVyZTFfdHMgPC0gdHMoZGF0YV9pbl9jbGVhbmVkJEZFQVRVUkVfMSxzdGFydCA9IGRhdGVfdHNfY2xlYW5lZFsxXSxmcmVxdWVuY3kgPSAyNTEuNjI1KQojbGluZXMoLShmZWF0dXJlMV90cykvMTAwLGNvbD0yKQpmZWF0dXJlMl90cyA8LSB0cyhkYXRhX2luX2NsZWFuZWQkRkVBVFVSRV8yLHN0YXJ0ID0gZGF0ZV90c19jbGVhbmVkWzFdLGZyZXF1ZW5jeSA9IDI1MS42MjUpCiNsaW5lcygtKGZlYXR1cmUyX3RzKS8xMDAsY29sPSJncmVlbiIpCmZlYXR1cmUzX3RzIDwtIHRzKGRhdGFfaW5fY2xlYW5lZCRGRUFUVVJFXzMsc3RhcnQgPSBkYXRlX3RzX2NsZWFuZWRbMV0sZnJlcXVlbmN5ID0gMjUxLjYyNSkKbGluZXMoKChmZWF0dXJlM190cykvMTAwMDApLGNvbD0iYmx1ZSIpCgojbGluZXMoKChmZWF0dXJlM190cy8xMDAwKSleezEvM30vMTAsY29sPSJibHVlIikKI2xpbmVzKHJlbWFpbmRlcihtYXJrZXRjbG9zZV90c19kZWNvbXApKQpgYGAKCgpgYGB7cn0KCnJlc2lkX2RlY29tcCA8LSBmb3JlY2FzdDo6cmVtYWluZGVyKG1hcmtldGNsb3NlX3RzX2RlY29tcCkKcmVzaWRfZGVjb21wX2xvZ3JldCA8LSBkaWZmKChyZXNpZF9kZWNvbXApKQp0aXRsZV9sb2dyZXQgPC0gIkRhaWx5IGxvZyByZXR1cm5zICglKSIKcGFyKG1mcm93ID0gYygyLCAyKSkKVFNBOjphY2YocmVzaWRfZGVjb21wX2xvZ3JldCwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2xvZ3JldCkKcGFjZihyZXNpZF9kZWNvbXBfbG9ncmV0LCBuYS5hY3Rpb24gPSBuYS5wYXNzLGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKCgojIChQKUFDRiBvZiBhYnNvbHV0ZSB2YWx1ZSBvZiBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQp0aXRsZV9hYnNsb2dyZXQgPC0gIkRhaWx5IGFicyBsb2cgcmV0dXJucyAoJSkiClRTQTo6YWNmKGFicyhyZXNpZF9kZWNvbXBfbG9ncmV0KSwgbmEuYWN0aW9uID0gbmEucGFzcywgbGFnLm1heCA9IDEwMCwgbWFpbiA9IHRpdGxlX2Fic2xvZ3JldCkKcGFjZihhYnMocmVzaWRfZGVjb21wX2xvZ3JldCksIG5hLmFjdGlvbiA9IG5hLnBhc3MsIGxhZy5tYXggPSAxMDAsIG1haW4gPSAiIikKIyAoUClBQ0Ygb2Ygc3F1YXJlZCBkYWlseSByZXR1cm5zCiNwYXIobWZyb3cgPSBjKDEsIDIpKQojVFNBOjphY2YoSShtYXJrZXRjbG9zZV9yZXRfY2xlYW5lZF4yKSwgbmEuYWN0aW9uID0gbmEucGFzcywgbWFpbiA9IHRpdGxlX3NwKQojcGFjZihJKG1hcmtldGNsb3NlX3JldF9jbGVhbmVkXjIpLCBuYS5hY3Rpb24gPSBuYS5wYXNzLCBtYWluID0gdGl0bGVfc3ApCgpgYGAKCmBgYHtyfQpwbG90KGFzLm51bWVyaWMocmVzaWRfZGVjb21wX2xvZ3JldCkgfiBhcy5udW1lcmljKGRpZmYoZmVhdHVyZTJfdHMpKSx4bGltPWMoLTEsMSkpCmxpbmVzKGxvd2Vzcyhhcy5udW1lcmljKHJlc2lkX2RlY29tcF9sb2dyZXQpIH4gYXMubnVtZXJpYyhkaWZmKGZlYXR1cmUyX3RzKSksIGY9MS81KSwKICAgICAgY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYApgYGB7cn0KcGxvdChhcy5udW1lcmljKHJlc2lkX2RlY29tcF9sb2dyZXQpIH4gYXMubnVtZXJpYygoZmVhdHVyZTNfdHMpLzEwMClbLWMoMSldLHhsaW09YygtMSwxKSkKbGluZXMobG93ZXNzKGFzLm51bWVyaWMocmVzaWRfZGVjb21wX2xvZ3JldCkgfiBhcy5udW1lcmljKChmZWF0dXJlM190cykvMTAwKVstYygxKV0sIGY9MS81KSwKICAgICAgY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xLjUpCmBgYAoKYGBge3J9CnBsb3QoYXMubnVtZXJpYyhyZXNpZF9kZWNvbXBfbG9ncmV0KSB+IGFzLm51bWVyaWMoZGlmZihmZWF0dXJlMV90cykpLHhsaW09YygtMSwxKSkKbGluZXMobG93ZXNzKGFzLm51bWVyaWMocmVzaWRfZGVjb21wX2xvZ3JldCkgfiBhcy5udW1lcmljKGRpZmYoZmVhdHVyZTFfdHMpKSwgZj0xLzUpLAogICAgICBjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEuNSkKYGBgCgpgYGB7cn0KZm9yX3N0bCA8LSBmb3JlY2FzdChtYXJrZXRjbG9zZV90c19kZWNvbXAsIGggPSAyMSkKcGxvdChmb3Jfc3RsKQpgYGAKYGBge3J9CmV4cChmb3Jfc3RsJG1lYW4pCmBgYAoKCmBgYHtyfQp0YWlsKG1hcmtldGNsb3NlX3RzKQpgYGAKCgoKIyAgU2VjdGlvbiBJSUk6IE1vZGVsIERldmVsb3BtZW50IGFuZCBhbmFseXNpcyAoUikKIyMjIyBEYWlseSBWb2xhdGlsaXR5IGJhc2VkIFRyZW5kIEZvbGxvd2luZyBhbmQgTWVhbiBSZXZlcnNpb24gU3dpdGNoaW5nCgooUGxlYXNlIGNsZWFyIHRoZSBlbnZpcm9ubWVudCBhbmQgYWxsIHZhcmlhYmxlcywgc3RhcnRpbmcgd2l0aCBhIGNsZWFuIHNsYXRlIGZyb20gaGVyZS4pCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KSSBoYXZlbid0IGNvbnNpZGVyZWQgdGhlIHByb3ZpZGVkIGZlYXR1cmVzIGluIHRoZSBkYXRhIGZvciB0aGlzIG1vZGVsIGRldmVsb3BtZW50IHNpbmNlIEkgZG9uJ3Qga25vdyB3aGF0IHRoZXkgcmVwcmVzZW50LCBob3cgdGhleSB3ZXJlIGV4dHJhY3RlZCBhbmQgd2hldGhlciBvciBub3QgdGhleSB3ZXJlIGxhZ2dlZCBieSBvbmUgdHJhZGluZyBkYXkuIEZ1cnRoZXIgaXQgc2VlbXMgYWxsIG9mIHRoZW0gYXJlIGVuZG9nZW5vdXMgKHNpbmNlIHRoZXkgc2VlbSB0byBiZSBleHRyYWN0ZWQgZnJvbSB0aGUgcHJpY2UgZGF0YSksIHNvIHRoZXkgc2hvdWxkbid0IGhhdmUgYW55IGV4dHJhICdpbmZvcm1hdGlvbicgKGluIGluZm9ybWF0aW9uIHRoZW9yZXRpY2FsIHNlbnNlIG9mIHRoZSB3b3JkKSBldmVuIHRob3VnaCB0aGV5IG1pZ2h0IGJlIGJldHRlciBwcmVkaWN0b3JzLiBIb3dldmVyLCBoZXJlIEkgZm9jdXMgb24gaW50ZXJwcmV0YWJpbGl0eSBvZiB0aGUgc3RyYXRlZ3kgaW4gdGVybXMgb2YgdmFyaWFibGVzIHdlbGwga25vd24gaW4gZmluYW5jaWFsIHRyYWRpbmcuCiAKIApUbyBlbmFibGUgaW50ZXJwcmV0YWJpbGl0eSBvZiB0aGUgbW9kZWwsIEkgdXNlIHdlbGwtZGVmaW5lZCBmZWF0dXJlcyBvciBmZWF0dXJlcyByZXByZXNlbnRpbmcgd2VsbC1kZWZpbmVkIHF1YW50aXRpZXMgcmVsYXRlZCB0byBhIHN0b2NrIHByaWNlIGVnLiB2b2xhdGlsaXR5LCBtb3ZpbmcgYXZlcmFnZXMsIHJlbGF0aXZlIHN0cmVuZ3RoIGluZGV4IGV0Yy4gVGhlIG1vZGVsIGVzc2VudGlhbGx5IHN3aXRjaGVzIGJldHdlZW4gZm9sbG93aW5nIGEgdHJlbmQgYW5kIG1lYW4gcmV2ZXJzaW9uIGJhc2VkIG9uIHRoZSBzdG9jayB2b2xhdGlsaXR5IChyZWxhdGl2ZSB0byBpdHMgaGlzdG9yaWNhbCB2YWx1ZXMpLiAKIApUaGUgYmFzaWMgc3RyYXRlZ3kgaXMgdGhpczogd2hlbiB0aGUgc3RvY2sgdm9sYXRpbGl0eSBpcyBoaWdoIHJlbGF0aXZlIHRvIGl0cyB1c3VhbCB2b2xhdGlsaXR5LCBJIHVzZSBtZWFuIHJldmVyc2lvbiBzdHJhdGVneSBiYXNlZCBvbiBSU0koMikuIFdoZW4gdGhlIHZvbGF0aWxpdHkgaXMgbG93IEkgZm9sbG93IHRoZSB0cmVuZCBiYXNlZCBvbiBNQS4gSW4gYWRkaXRpb24gaW4gc2xvdyB2b2xhdGlsaXR5IHJlZ2ltZSBpZiB0aGUgUlNJKDIpIGdldHMgdmVyeSBoaWdoLCB0aGlzIGluZGljYXRlcyBvdmVyc29sZCBjb25kaXRpb24gaW4gd2hpY2ggY2FzZSBJIHNob3J0LiBJIGZ1cnRoZXIgc2hvdyBpbXByb3ZlbWVudCBieSB1cGRhdGluZyB0aGUgdGhyZXNob2xkIHBhcmFtZXRlcnMgZXZlcnkgNSBkYXlzIGJ5IGlkZW50aWZ5aW5nIGJlc3QgbW9kZWwgcGFyYW1ldGVycyB1c2luZyBncmlkIHNlYXJjaC4gUGVyZm9ybWFuY2UgaXMganVkZ2VkIG9uIHRoZSBiYXNpcyBvZiBjdW11bGF0aXZlIFBOTCB3cnQgYWx3YXlzIGJ1eSBzdHJhdGVneS4gVGhlIHByZWRpY3Rpb24gZm9yIG5leHQgYWxzbyBpbXByb3ZlcyBhbmQgaXMgLTEgZnJvbSB0aGUgdXBkYXRpbmcgc3RyYXRlZ3kuCiAKVGhpcyBzdHJhdGVneSBjYW4gYmUgcG90ZW50aWFsbHkgZnVydGhlciBpbXByb3ZlZCBieSB1c2luZyBtb21lbnR1bSwgd2hpY2ggSSBleHBlY3QgdG8gYmUgcGFydGljdWxhcmx5IHN0cm9uZyBkdXJpbmcgZG93bnR1cm5zLiBCdXQgaXQgaGFzIHRvIGJlIGJhbGFuY2VkIGFnYWluc3QgYmVpbmcgJ3Rvby1sYXRlJyB0cnlpbmcgdG8gdGltZSB0aGUgc3RvY2sgcHJpY2UuIEFub3RoZXIgcG90ZW50aWFsIGltcHJvdmVtZW50IGlzIHRvIHVzZSB2b2xhdGlsaXR5IGZvcmVjYXN0IHVzaW5nIGEgTWFya292IHN3aXRjaGluZyBiYXNlZCBHQVJDSCBtb2RlbCAoc2luY2UgdGhleSBzZWVtIHRvIGhhdmUgYmVlbiBzaG93biBtb3JlIGFjY3VyYXRlIGluIHByZWRpY3Rpbmcgc2hvcnQgdGVybSB2b2xhdGlsaXR5IDx3ZWVrIHRoYW4gZ2VuZXJhbCBHQVJDSCBtb2RlbHMpIG9yIEdBUkNILUFSSU1BIG1vZGVsLiBJIHVzZSBSIGhlcmUgaW5zdGVhZCBvZiBweXRob24sIGhvd2V2ZXIgYSBzbWFsbCBzbmlwcGV0IG9mIGNvZGUgdG8gY2xlYXVwIHRoZSBkYXRhIGlzIGluIHB5dGhvbjMuCgpgYGB7cn0Kcm0obGlzdD1scygpKQojIyBTZXR1cAojIyBHZXQgc29tZSBmdW5jdGlvbnMvcGFja2FnZXMKIyMgaW5zdGFsbC5wYWNrYWdlcygiUkN1cmwiKQpyZXF1aXJlKFJDdXJsKQpzaXQgPSBnZXRVUkxDb250ZW50KCdodHRwczovL2dpdGh1Yi5jb20vc3lzdGVtYXRpY2ludmVzdG9yL1NJVC9yYXcvbWFzdGVyL3NpdC5neicsIGJpbmFyeT1UUlVFLCBmb2xsb3dsb2NhdGlvbiA9IFRSVUUsIHNzbC52ZXJpZnlwZWVyID0gRkFMU0UpCmNvbiA9IGd6Y29uKHJhd0Nvbm5lY3Rpb24oc2l0LCAncmInKSkKc291cmNlKGNvbikKY2xvc2UoY29uKQpgYGAKCmBgYHtyfQojIyBHZXQgZGF0YSwgdGhpcyBpcyBvcmlnaW5hbCBmaWxlIGRvd25sb2FkZWQgd2l0aG91dCBhbnkgY2hhbmdlcwpzZXR3ZCgifi9Eb2N1bWVudHMvaW50ZXJ2aWV3X3ByZXAvUXVlc3Rpb24xIikKZGF0YV9pbiA8LSByZWFkLmNzdihmaWxlID0gJ0RhdGFTZXQuY3N2JykKZGF0ZV90cyA8LSBhcy5EYXRlKGRhdGFfaW4kREFURSwgZm9ybWF0ID0gIiVtLyVkLyVZIikKbGlicmFyeSh4dHMpCm1hcmtldGNsb3NlX3h0cyA8LSB4dHMoZGF0YV9pbiRNQVJLRVRDTE9TRSxvcmRlci5ieSA9IGRhdGVfdHMpCm1hcmtldGNsb3NlX3JldCA8LSAxMDAqZGlmZihsb2cobWFya2V0Y2xvc2VfeHRzKSkKCmBgYAoKYGBge3J9CiNFc3RpbWF0ZSBoaXN0b3JpY2FsIHJlbGF0aXZlIHZvbGF0aWxpdHkKbGlicmFyeShxdWFudG1vZCkKcmV0LmxvZyA8LSBST0MobWFya2V0Y2xvc2VfeHRzLCB0eXBlPSdjb250aW51b3VzJykKaGlzdC52b2wgPC0gcnVuU0QocmV0LmxvZywgbiA9IDIxKQp2b2wucmFuayA8LSBwZXJjZW50LnJhbmsoU01BKHBlcmNlbnQucmFuayhoaXN0LnZvbCwgMjUyKSwgMjEpLCAyNTApCmBgYAoKCmBgYHtyfQojIyBNZWFuIHJldmVyc2lvbiBmZWF0dXJlCnJzaTJfdmFsdWVzIDwtIFJTSShtYXJrZXRjbG9zZV94dHMsMikKCiMjIFRyZW5kIGZvbGxvd2luZyBmZWF0dXJlCnNtYS5zaG9ydCA8LSAgU01BKG1hcmtldGNsb3NlX3h0cywgNSkKc21hLmxvbmcgPC0gU01BKG1hcmtldGNsb3NlX3h0cywgMjApCgpgYGAKCgpgYGB7cn0KIyMgVHJhZGluZyBzdHJhdGVneTogSGlnaCBWb2xhdGlsaXR5IC0gTWVhbiByZXZlcnNpb24sIExvdyB2b2xhdGlsaXR5IC0gZm9sbG93IHRyZW5kCiMgbG9uZyBpZiB2b2wucmFuayA+IDAuNTAgYW5kIHJzaTJfdmFsdWVzIDwgNTAgKG1lYW4gcmV2ZXJzaW9uKSBzaG9ydCBvdGhlcndpc2UKIyBzaG9ydCBpZiB2b2wucmFuayA8IDAuNTAgYW5kIAojIHJzaTJfdmFsdWVzID4gODAgKG92ZXJib3VnaHQpIG9yIDUgd2VlayBNQSA8IDIwIHdlZWsgTUEKIyBsb25nIG90aGVyd2lzZQojIEFkZCBsYWcgdG8gcHJlZGljdCBuZXh0IHZhbHVlIGZyb20gY3VycmVudCBlc3RpbWF0ZXMKIyAKIyBgaWlmYCBpcyBtb3JlIHN0YWJsZSB2ZXJzaW9uIG9mIGBpZmVsc2UoKWAgZnVuY3Rpb24gaW4gUiB0byBncmFjZWZ1bGx5IGhhbmRsZSBgTkFgIGFuZCBgSW5mYCBlbnRyaWVzCgpzaWcgPC0gTGFnKGlpZih2b2wucmFuayA+IDAuNTAsIAogICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXMgPCA1MCAsIDEsIC0xKSwKICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydCA8IHNtYS5sb25nIHwgcnNpMl92YWx1ZXMgPiA4MCAsIC0xLCAxKQopKQoKYGBgCgpgYGB7cn0KIyMgRXZhbHVhdGUKIyBXZSBldmFsdWF0ZSB0aGUgc3RyYXRlZ3kgYWdhaW5zdCBhbHdheXMgYnV5IHN0cmF0ZWd5IGluIGNvbHVtbiBDVU1TVU0gaW4gZGF0YS4gTW9tZW50dW0gaXMganVzdCB0aGUgZGlmZmVyZW5jZSBvZiBzdWNjZXNzaXZlIGVudHJpZXMuIE5vdGluZyBoZXJlIHRoYXQgc2lnIGlzIGRlbGF5ZWQgYnkgMSB0cmFkaW5nIGRheS4KcHJpbnQoJ0NVTVNVTSBjb2x1bW4gZnJvbSBkYXRhOicpCnByaW50KGhlYWQoZGF0YV9pbiRDVU1TVU1bLWMoMSldKSkKcHJpbnQoIkFsd2F5cyBidXkgc3RyYXRlZ3kiKQpyZXQgPC0gMTAwMCptb21lbnR1bShtYXJrZXRjbG9zZV94dHMpKjEKcHJpbnQoaGVhZChjdW1zdW0ocmV0Wy1jKDEpXSkpKQoKYGBgCgpgYGB7cn0KIyMgUGxvdCBjdW11bGF0aXZlIFBOTApyZXQgPC0gMTAwMCptb21lbnR1bShtYXJrZXRjbG9zZV94dHMpKnNpZwplcSA8LSAoY3Vtc3VtKHJldFstYygxKV0pKQpwbG90KGNvcmVkYXRhKGVxKX5kYXRlX3RzWy1jKDEpXSx0eXBlPSdsJyx4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpncmlkKGNvbD0xLGx3ZD0xKQpsaW5lcyhkYXRhX2luJENVTVNVTVstYygxKV1+ZGF0ZV90c1stYygxKV0sY29sPSJyZWQiKQpsZWdlbmQoeD0idG9wbGVmdCIsbGVnZW5kPWMoIlZvbGF0aWxpdHkgYmFzZWQgc3dpdGNoaW5nIiwiQnV5IERhaWx5IChHaXZlbikiKSwKICAgICAgIGZpbGw9YygiYmxhY2siLCJyZWQiKSkKCmBgYAoKYGBge3J9CiMjIENvbXBhcmUgY3VtdWxhdGl2ZSBQTkwgYXQgdGhlIGVuZApwcmludChkYXRhLmZyYW1lKCdHaXZlbiBTdHJhdGVneSc9dGFpbChkYXRhX2luJENVTVNVTVstYygxKV0pLCdQcm9wb3NlZCBTdHJhdGVneSc9dGFpbChhcy52ZWN0b3IoZXEpKSxyb3cubmFtZXMgPSBhcy5jaGFyYWN0ZXIodGFpbChkYXRlX3RzKSkpKQpgYGAKCmBgYHtyfQojIyBPcHRpbWl6ZSB0aHJlc2hvbGRpbmcgcGFyYW1ldGVycyB1c2luZyBncmlkIHNlYXJjaDoKIyBQYXJhbWV0ZXJzOiB2b2wucmFua19NSU4sIHJzaTJfdmFsdWVzX01BWCwgcnNpMl92YWx1ZXNfTUlOCiMgc2lnLnRlc3QgPC0gIChpaWYodm9sLnJhbmsudGVzdCA+IHZvbC5yYW5rX01JTiwgCiMgICAgICAgICAgICAgICAgICAgaWlmKChyc2kyX3ZhbHVlcy50ZXN0IDwgcnNpMl92YWx1ZXNfTUFYKSAsIDEsIC0xKSwKIyAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0LnRlc3QgPCBzbWEubG9uZy50ZXN0IHwgKChyc2kyX3ZhbHVlcy50ZXN0ID4gcnNpMl92YWx1ZXNfTUlOKSksIC0xLCAxKQoKIyB3ZSB3aWxsIHVzZSBmb3JlYWNoIGZvciBmYXN0ZXIgZm9yIGxvb3BzCiMgYXV0b21hdGljIGluc3RhbGwgb2YgcGFja2FnZXMgaWYgdGhleSBhcmUgbm90IGluc3RhbGxlZCBhbHJlYWR5Cmxpc3Qub2YucGFja2FnZXMgPC0gYygKICAiZm9yZWFjaCIsCiAgImRvUGFyYWxsZWwiLAogICJyYW5nZXIiLAogICJwYWxtZXJwZW5ndWlucyIsCiAgInRpZHl2ZXJzZSIsCiAgImthYmxlRXh0cmEiCikKCm5ldy5wYWNrYWdlcyA8LSBsaXN0Lm9mLnBhY2thZ2VzWyEobGlzdC5vZi5wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpWywiUGFja2FnZSJdKV0KCmlmKGxlbmd0aChuZXcucGFja2FnZXMpID4gMCl7CiAgaW5zdGFsbC5wYWNrYWdlcyhuZXcucGFja2FnZXMsIGRlcD1UUlVFKQp9CgojbG9hZGluZyBwYWNrYWdlcwpmb3IocGFja2FnZS5pIGluIGxpc3Qub2YucGFja2FnZXMpewogIHN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcygKICAgIGxpYnJhcnkoCiAgICAgIHBhY2thZ2UuaSwgCiAgICAgIGNoYXJhY3Rlci5vbmx5ID0gVFJVRQogICAgKQogICkKfQoKYGBgCgoKYGBge3J9CiMgQ3JlYXRlIHN1YnNldCBhcnJheXMgdG8gdGVzdCBob3cgbXVjaCB0aGVzZSBwYXJhbWV0ZXJzIGNoYW5nZSBvdmVyIHRpbWUKIyBFdmVyeSA1IGRheSB1cGRhdGUgb2YgcGFyYW1ldGVycwpzdWJfc2VyLmxlbiA8LSA1CnN0YXJ0X3BvaW50IDwtIDcwMApUbWF4X2FycmF5IDwtIHNlcShzdGFydF9wb2ludCxsZW5ndGgobWFya2V0Y2xvc2VfeHRzKSxzdWJfc2VyLmxlbikKCiMgR3JpZCBzZWFyY2ggZm9yIHBhcmFtZXRycyB0aGF0IG1heGltaXplIGN1bXVsYXRpdmUgcmV0dXJuIG92ZXIgc3Vic2V0IGFycmF5CiMgYW5kIHN0b3JlIHRoZSBwcmVkaWN0aW9uIGZvciBuZXh0IGRheQpyZXR1cm5fbWF4LnRlc3QgPC0gbWF0cml4KC1JbmYsbGVuZ3RoKFRtYXhfYXJyYXkpLDEpCnByZWRpY3RfYXJyYXkudGVzdCA8LSBtYXRyaXgoMSxsZW5ndGgoVG1heF9hcnJheSksMSkKbWF4X3Bhci50ZXN0IDwtIG1hdHJpeCgwLGxlbmd0aChUbWF4X2FycmF5KSwzKQpgYGAKCgoKYGBge3J9CiMgUmVhZCBmcm9tIHNhdmVkIGZpbGUgZm9yIHJldGVzdGluZwptYXhfcGFyLnRlc3QgPC0gcmVhZC5jc3YoIm1heF9wYXIudGVzdF81ZGF5NzAwXzMwX3JzaTJNSU5yYW5nZS5jc3YiKVssYygiVjEiLCJWMiIsIlYzIildCmBgYAoKCgpgYGB7cn0KCnByaW50KCJTdGFydGluZyBncmlkIHNlYXJjaC4gVGhpcyBtaWdodCB0YWtlIGEgd2hpbGUgKDUtMTAgbWludXRlcykiKQpmb3JlYWNoIChpID0gIDE6bGVuZ3RoKFRtYXhfYXJyYXkpKSAlZG8lIHsKICBwcmludChwYXN0ZSgiVXBkYXRpbmcgdGhyZXNob2xkIHBhcmFtZXRlcnMgZm9yIHRoZSB3ZWVrIixpLCJvZiIsbGVuZ3RoKFRtYXhfYXJyYXkpKSkKICBUbWF4IDwtIFRtYXhfYXJyYXlbaV0KICBtYXJrZXRjbG9zZV94dHMudGVzdCA8LSBtYXJrZXRjbG9zZV94dHNbMTpUbWF4XQogIG1hcmtldGNsb3NlX3JldC50ZXN0IDwtIDEwMCpkaWZmKGxvZyhtYXJrZXRjbG9zZV94dHMudGVzdCkpWy1jKDEpXQogICNIaXN0b3JpY2FsIHZvbGF0aWxpdHkKICByZXQubG9nLnRlc3QgPSBST0MobWFya2V0Y2xvc2VfeHRzLnRlc3QsIHR5cGU9J2NvbnRpbnVvdXMnKQogIGhpc3Qudm9sLnRlc3QgPSBydW5TRChyZXQubG9nLnRlc3QsIG4gPSAyMSkKICB2b2wucmFuay50ZXN0ID0gcGVyY2VudC5yYW5rKFNNQShwZXJjZW50LnJhbmsoaGlzdC52b2wudGVzdCwgMjUyKSwgMjEpLCAyNTApCiAgIyBNZWFuIHJldmVyc2lvbgogIHJzaTJfdmFsdWVzLnRlc3QgPSBSU0kobWFya2V0Y2xvc2VfeHRzLnRlc3QsMikKICAjIFRyZW5kIGZvbGxvd2luZwogIHNtYS5zaG9ydC50ZXN0IDwtICBTTUEobWFya2V0Y2xvc2VfeHRzLnRlc3QsIDUpCiAgc21hLmxvbmcudGVzdCA8LSBTTUEobWFya2V0Y2xvc2VfeHRzLnRlc3QsIDIwKQogICMgR3JpZCBzZWFyY2gKICBmb3JlYWNoICh2b2wucmFua19NSU4gPSBzZXEoMC4xLDAuOCwwLjAxKSkgJWRvJSB7IAogICAgZm9yIChyc2kyX3ZhbHVlc19NQVggaW4gc2VxKDMwLDgwLDIpKSB7CiAgICAgIGZvciAocnNpMl92YWx1ZXNfTUlOIGluIDgwKXsgI3NlcSgzMCw4MCwxMCkpIHsgIyBObyBlZmZlY3Qgb2YgY2hhbmdlcwogICAgICAgICMgUHJlZGljdGlvbiBzaWduYWwgY2FsY3VsYXRpb24KICAgICAgICBzaWcudGVzdCA8LSAgKGlpZih2b2wucmFuay50ZXN0ID4gdm9sLnJhbmtfTUlOLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoKHJzaTJfdmFsdWVzLnRlc3QgPCByc2kyX3ZhbHVlc19NQVgpICwgMSwgLTEpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGlpZihzbWEuc2hvcnQudGVzdCA8IHNtYS5sb25nLnRlc3QgfCAoKHJzaTJfdmFsdWVzLnRlc3QgPiByc2kyX3ZhbHVlc19NSU4pKSwgLTEsIDEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgKSkKICAgICAgICAjIFNhdmUgdGhpcyBmb3IgZnV0dXJlIHVzZQogICAgICAgIHByZWRpY3QudGVzdCA8LSB0YWlsKHNpZy50ZXN0LDEpCiAgICAgICAgCiAgICAgICAgIyBMYWcgc2lnbmFsIGJ5IG9uZSB0cmFkaW5nIGRheQogICAgICAgIHNpZy50ZXN0IDwtIExhZyhzaWcudGVzdCkKICAgICAgICByZXQudGVzdCA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cy50ZXN0KSpzaWcudGVzdAogICAgICAgIAogICAgICAgIHJldHVybl90b3RhbC50ZXN0IDwtIHRhaWwoY3Vtc3VtKHJldC50ZXN0Wy1jKDEpXSksMSkKCiAgICAgICAgIyBTYXZlIGlmIEN1bXVsYXRpdmUgcmV0dXJuIGlzIG1vcmUgdGhhbiBjdXJyZW50IG1heGltdW0KICAgICAgICBpZiAocmV0dXJuX3RvdGFsLnRlc3QgPiByZXR1cm5fbWF4LnRlc3RbaV0pIHsKICAgICAgICAgIAogICAgICAgICAgcmV0dXJuX21heC50ZXN0W2ldIDwtIHJldHVybl90b3RhbC50ZXN0CiAgICAgICAgICBtYXhfcGFyLnRlc3RbaSwxXSA8LSB2b2wucmFua19NSU4KICAgICAgICAgIG1heF9wYXIudGVzdFtpLDJdIDwtIHJzaTJfdmFsdWVzX01BWAogICAgICAgICAgbWF4X3Bhci50ZXN0W2ksM10gPC0gcnNpMl92YWx1ZXNfTUlOCiAgICAgICAgICBwcmVkaWN0X2FycmF5LnRlc3RbaV0gPC0gcHJlZGljdC50ZXN0CiAgICAgICAgICBzaWcudGVzdC5tYXggPC0gc2lnLnRlc3QKICAgICAgICAgIAogICAgICAgIH0KICAgICAgICAKICAgICAgfQogICAgICAKICAgIH0KICAgIAogIH0KfQojIFNhdmUgZm9yIGZ1dHV0cmUgcmV1c2UKIyB3cml0ZS5jc3YobWF4X3Bhci50ZXN0LCJtYXhfcGFyLnRlc3RfNWRheTcwMF8zMF9yc2kyTUlOcmFuZ2UuY3N2IikKCmBgYAoKYGBge3J9CiMgUHJpbnQgb3B0aW1pemVkIHBhcmFtZXRlcnMgZm9yIGVhY2ggc3ViIHRpbWUgc2VyaWVzCnByaW50KG1heF9wYXIudGVzdCkKYGBgCgpgYGB7cn0Kb3B0aW1pemVkLnBhcnNfNWRheXMgPC0gZGF0YS5mcmFtZSgndm9sLnJhbmtfTUlOJz1tYXhfcGFyLnRlc3RbLDFdLCdyc2kyX3ZhbHVlc19NQVgnPW1heF9wYXIudGVzdFssMl0sJ3JzaTJfdmFsdWVzX01JTic9bWF4X3Bhci50ZXN0WywzXSxyb3cubmFtZXMgPSBkYXRlX3RzW1RtYXhfYXJyYXldKQpwcmludChvcHRpbWl6ZWQucGFyc181ZGF5cykKIyBXZSB3aWxsIHVzZSB2YWx1ZXMgKDAuNjcsNDAsODApIGZvciBkYXRlcyBhZnRlciBhbmQgbm90IGluY2x1ZGluZyAyMDA0LTA5LTEwIGFuZCBiZWZvcmUgYW5kIGluY2x1ZGluZyAyMDA0LTExLTE5IGFuZCBzbyBvbi4KCm9wdGltaXplZC5wYXJzXzVkYXlzIDwtIGRhdGEuZnJhbWUoJ3ZvbC5yYW5rX01JTic9cmVwKG1heF9wYXIudGVzdFsxOmxlbmd0aChUbWF4X2FycmF5KSwxXSxlYWNoPXN1Yl9zZXIubGVuKSwncnNpMl92YWx1ZXNfTUFYJz1yZXAobWF4X3Bhci50ZXN0WzE6bGVuZ3RoKFRtYXhfYXJyYXkpLDJdLGVhY2g9c3ViX3Nlci5sZW4pLCdyc2kyX3ZhbHVlc19NSU4nPXJlcChtYXhfcGFyLnRlc3RbMTpsZW5ndGgoVG1heF9hcnJheSksM10sZWFjaD1zdWJfc2VyLmxlbikpCnByaW50KGRpbSgob3B0aW1pemVkLnBhcnNfNWRheXMpKSkKIyBwcmUtcGVuZCB2YWx1ZXMgZm9yIGZpcnN0IDcwMCBkYXlzCmluaXRpYWwucGFyc183MDBkYXlzIDwtIGRhdGEuZnJhbWUoJ3ZvbC5yYW5rX01JTic9cmVwKC41MCxlYWNoPXN0YXJ0X3BvaW50KSwncnNpMl92YWx1ZXNfTUFYJz1yZXAoNTAsZWFjaD1zdGFydF9wb2ludCksJ3JzaTJfdmFsdWVzX01JTic9cmVwKDgwLGVhY2g9c3RhcnRfcG9pbnQpKQpvcHRpbWl6ZWQucGFyc181ZGF5cyA8LSByYmluZChpbml0aWFsLnBhcnNfNzAwZGF5cyxvcHRpbWl6ZWQucGFyc181ZGF5cykKb3B0aW1pemVkLnBhcnNfNWRheXMgPC0gb3B0aW1pemVkLnBhcnNfNWRheXNbMTpsZW5ndGgobWFya2V0Y2xvc2VfeHRzKSxdCnByaW50KGhlYWQob3B0aW1pemVkLnBhcnNfNWRheXMpKQpwcmludCh0YWlsKG9wdGltaXplZC5wYXJzXzVkYXlzKSkKYGBgCgoKYGBge3J9CiMjIEV2YWx1YXRlIHBlcmZvcm1hbmNlIG9mIG5ldyBwYXJhbWV0ZXJzCiMjIE9wdGltaXplZCB0cmFkaW5nIHNpZ25hbApzaWdfb3B0IDwtIExhZyhpaWYodm9sLnJhbmsgPiBvcHRpbWl6ZWQucGFyc181ZGF5cyR2b2wucmFua19NSU4sIAogICAgICAgICAgICAgICAgICAgaWlmKHJzaTJfdmFsdWVzIDwgb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUFYICAsIDEsIC0xKSwKICAgICAgICAgICAgICAgICAgIGlpZihzbWEuc2hvcnQgPCBzbWEubG9uZyB8IHJzaTJfdmFsdWVzID4gb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUlOLCAtMSwgMSkKKSkKYGBgCgoKYGBge3J9CiMjUGxvdCBvZiBjdW11bGF0aXZlIHJldHVybnMKcmV0X29wdCA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cykqc2lnX29wdAplcV9vcHQgPC0gKGN1bXN1bShyZXRfb3B0Wy1jKDEpXSkpCnBsb3QoY29yZWRhdGEoZXFfb3B0KX5kYXRlX3RzWy1jKDEpXSx0eXBlPSdsJyxjb2w9ImdyZWVuIix4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpsaW5lcyhjb3JlZGF0YShlcSl+ZGF0ZV90c1stYygxKV0pCmxpbmVzKGRhdGFfaW4kQ1VNU1VNWy1jKDEpXX5kYXRlX3RzWy1jKDEpXSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEpCmxlZ2VuZCh4PSJ0b3BsZWZ0IixsZWdlbmQ9YygiNWRheSB1cGRhdGUgc3c6IExpdmUgdHJhZGluZyArIGJhY2t0ZXN0aW5nIiwiSW5pdGFpbCBwYXIgc3dpdGNoaW5nIGJhY2t0ZXN0aW5nIiwiQnV5IERhaWx5IChHaXZlbikiKSwKICAgICAgIGZpbGw9YygiZ3JlZW4iLCJibGFjayIsInJlZCIpKQojQ2xlYXJseSBuZXcgcGFyYW1ldGVycyBwZXJmb3JtIGJldHRlcgpgYGAKCmBgYHtyfQojIFdlIGNhbiBhbHNvIHVzZSB0aGUgZW50aXJldHkgb2YgdGltZSBzZXJpZXMgZm9yIGJhY2t0ZXN0aW5nLCB0byBzZWUgd2hhdCBjb3VsZCBoYXZlIGJlZW4gdGhlIHBlcmZvcm1hbmNlIGlmIHdlIGtuZXcgdGhlIGJlc3QgcGFyYW1ldGVyIGVzdGltYXRlcyBpbiB0aGUgcGFzdCAob3IgdHJ5IGhpdCBhbmQgdHJpYWwvb3RoZXIgb3B0aW1pemF0aW9uIHRvIGltcHJvdmUgb3ZlcmFsbCBwZXJmb3JtYW5jZSB3aXRoIHNpbmdsZSBzZXQgb2YgcGFyYW1ldGVycyBmb3IgYWxsIHRpbWUgcG9pbnRzKSBhbmQgdXNlIGl0IHRvIGZ1cnRoZXIgaW1wcm92ZSB0aGUgdXBkYXRlIGFwcHJvYWNoIApzaWdfb3B0X29wdGltYWwgPC0gTGFnKGlpZih2b2wucmFuayA+IDAuMiwgCiAgICAgICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXMgPCAzNiAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0IDwgc21hLmxvbmcgfCByc2kyX3ZhbHVlcyA+IDgwLCAtMSwgMSkKKSkKcmV0X29wdF9vcHRpbWFsIDwtIDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKSpzaWdfb3B0X29wdGltYWwKZXFfb3B0X29wdGltYWwgPC0gKGN1bXN1bShyZXRfb3B0X29wdGltYWxbLWMoMSldKSkKcGxvdChjb3JlZGF0YShlcV9vcHRfb3B0aW1hbCl+ZGF0ZV90c1stYygxKV0sY29sPSJibHVlIix0eXBlPSdsJyx4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpsaW5lcyhjb3JlZGF0YShlcV9vcHQpfmRhdGVfdHNbLWMoMSldLGNvbD0iZ3JlZW4iKQpsaW5lcyhjb3JlZGF0YShlcSl+ZGF0ZV90c1stYygxKV0pCmxpbmVzKGRhdGFfaW4kQ1VNU1VNWy1jKDEpXX5kYXRlX3RzWy1jKDEpXSxjb2w9InJlZCIpCmdyaWQoY29sPTEsbHdkPTEpCmxlZ2VuZCh4PSJ0b3BsZWZ0IixsZWdlbmQ9YygiNWRheSB1cGRhdGUgc3cgTGl2ZXRyYWRpbmcgKyBCYWNrdGVzdGluZyIsIkluaXRpYWwgcGFyIHN3aXRjaGluZzogQmFja3Rlc3RpbmciLCJCdXkgRGFpbHkgKEdpdmVuKSIsIk9wdGltYWwgcGFyOiBCYWNrdGVzdGluZyIpLAogICAgICAgZmlsbD1jKCJncmVlbiIsImJsYWNrIiwicmVkIiwiYmx1ZSIpKQpgYGAKCmBgYHtyfQojQ29tcGFyZSBjdW11bGF0aXZlIFBOTCBhdCB0aGUgZW5kIGFmdGVyIGltcHJvdmVtZW50cwpwcmludChkYXRhLmZyYW1lKCdHaXZlbiBTdHJhdGVneSc9dGFpbChkYXRhX2luJENVTVNVTVstYygxKV0pLCdGaXhQYXJTd2l0Y2hpbmcnPXRhaWwoYXMudmVjdG9yKGVxKSksJzVkYXlVcGRhdGVTdyc9dGFpbChhcy52ZWN0b3IoZXFfb3B0KSksJ09wdGltYWwnPXRhaWwoYXMudmVjdG9yKGVxX29wdF9vcHRpbWFsKSkscm93Lm5hbWVzID0gYXMuY2hhcmFjdGVyKHRhaWwoZGF0ZV90cykpKSkKCmBgYApgYGB7cn0KIyBQcmVkaWN0aW9ucyBmb3IgbmV4dCBkYXkgdXNpbmcgdGhlc2UgcGFyYW1ldGVyczoKIyBQcmVkaWN0aW9uIGZyb20gb3B0aW1hbCB3aWxsIGJlIHNhbWUgYXMgb3B0IHNpbmNlIHRoZSBwYXJhbWV0ZXJzIGFyZSB0aGUgc2FtZQojIG9uIGxhc3QgZGF5Cmxhc3RkYXlfcm93IDwtIGxlbmd0aChtYXJrZXRjbG9zZV9yZXQpCgpwcmVkaWN0aW9uX2ZpeGVkIDwtIGlpZih2b2wucmFua1tsYXN0ZGF5X3Jvd10gPiAwLjUwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXNbbGFzdGRheV9yb3ddIDwgNTAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydFtsYXN0ZGF5X3Jvd10gPCBzbWEubG9uZ1tsYXN0ZGF5X3Jvd10gfCByc2kyX3ZhbHVlc1tsYXN0ZGF5X3Jvd10gPiA4MCAsIC0xLCAxKQopCgpwcmVkaWN0aW9uX29wdCA8LSBpaWYodm9sLnJhbmtbbGFzdGRheV9yb3ddID4gb3B0aW1pemVkLnBhcnNfNWRheXMkdm9sLnJhbmtfTUlOW2xhc3RkYXlfcm93XSwgCiAgICAgICAgICAgICAgICAgICAgICBpaWYocnNpMl92YWx1ZXNbbGFzdGRheV9yb3ddIDwgb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUFYW2xhc3RkYXlfcm93XSAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0W2xhc3RkYXlfcm93XSA8IHNtYS5sb25nW2xhc3RkYXlfcm93XSB8IHJzaTJfdmFsdWVzID4gb3B0aW1pemVkLnBhcnNfNWRheXMkcnNpMl92YWx1ZXNfTUlOW2xhc3RkYXlfcm93XSwgLTEsIDEpCikKCiMgRml4ZWQgcGFyYW1ldGVyIHN3aXRjaGluZyBmYWlscyB0byBwcmVkaWN0IGNvcnJlY3RseSBidXQgNSBkYXkgdXBkYXRlIHBhcmFtZXRlciB1cGRhdGUgZG9lcyBjb3JyZWN0bHkgcHJlZGljdCAtMQpwcmludChwYXN0ZTAoIlByZWRpY3Rpb24gZm9yIG5leHQgZGF5OiAgIixwcmVkaWN0aW9uX29wdCkpCmBgYAoKVGhpcyB3YXMgYWxzbyBldmlkZW50IGluIHRoZSBleHBsb3JhdGlvbiBwbG90cyBmb3Igd2Vla2RheSBzcGVjaWZpYyB0cmVuZHMgb24gcmV0dXJucwoKIyMgU2VjdGlvbiBJSUk6IE1vZGVsIERldmVsb3BtZW50IGFuZCBhbmFseXNpczogUGFydCBJSQojIyAgV2Vla2RheSBTcGVjaWZpYyBVcGRhdGUKCltVc2luZyB3ZWVrZGF5IHNwZWNpZmljIGRhaWx5IHJldHVybiB2b2xhdGlsaXR5ICh3ZWVrbHkgYmFycykgZm9yIFRGIGFuZCBNUiBzd2l0Y2hpbmddCgpgYGB7cn0KIyMgRXhwbG9pdGluZyB3ZWVrIG9mIHRoZSBkYXkgcGF0dGVybnMgaW4gcmV0dXJucwptYXJrZXRjbG9zZV93ZWVrZGF5IDwtIGJhc2U6OndlZWtkYXlzKGRhdGVfdHMsYWJicmV2aWF0ZT1UUlVFKQoKIyMgTW9kZWxpbmcgTW9uZGF5IHJldHVybnMKbWFya2V0Y2xvc2VfcmV0X01vbiA8LSBtYXJrZXRjbG9zZV9yZXRbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0KcGxvdCh4PWRhdGVfdHNbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0seT1hcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikseWxpbT1jKC0xLjUsMS41KSwKICAgICB4bGFiPSJZZWFyIix5bGFiID0gIiUgQ2hhbmdlIix0eXBlID0gJ2wnLGNvbD0icGluayIsCiAgICAgbWFpbj0iJSBXZWVrbHkgY2hhbmdlIGluIE1vbmRheSBkYWlseSBsb2cgcmV0dXJucyIpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikgfiBkYXRlX3RzW21hcmtldGNsb3NlX3dlZWtkYXkgPT0gIk1vbiJdICxmPTEvMiksY29sPSJyZWQiLGx3ZD0xKQpsaW5lcyhsb3dlc3MoYXMudmVjdG9yKG1hcmtldGNsb3NlX3JldF9Nb24pICB+IGRhdGVfdHNbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0sZj0xLzQpLGNvbD0iYmx1ZSIsbHdkPTEpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikgIH4gZGF0ZV90c1ttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSxmPTEvOCksY29sPSJncmVlbiIsbHdkPTEpCmxpbmVzKGxvd2Vzcyhhcy52ZWN0b3IobWFya2V0Y2xvc2VfcmV0X01vbikgIH4gZGF0ZV90c1ttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSxmPTEvMTIpLGNvbD0iYmxhY2siLGx3ZD0xKQpncmlkKGNvbD0xLGx3ZD0xKQpsZWdlbmQoeCA9ICJib3R0b21sZWZ0IixsZWdlbmQ9YygiJSBsb2cgcmV0IiwgImY9MS8yIGxvd2VzcyIsImY9MS80IiwgImY9MS84IiwiZj0xLzIiKSwKICAgICAgIGZpbGwgPSBjKCJwaW5rIiwicmVkIiwiYmx1ZSIsImdyZWVuIiwiYmxhY2siKSwgYnR5PSduJykKYGBgCgoKYGBge3J9CiMjIE1vbmRheSByZXR1cm5zIGNsZWFybHkgc2hvdyBhIHZpc2libGUgcGF0dGVybiwgd2UgY2FuIGNyZWF0ZSBhIHRpbWUgc2VyaWVzIGZvciByZXR1cm5zIGFuZCB1c2UgaXRzIHByZWRpY3RhYmlsaXR5IHRvIGltcHJvdmUgdGhlIGVhcmxpZXIgZm9yZWNhc3QKIyMgU3dpdGNoaW5nIHN0cmF0ZWd5IGJhc2VkIG9uIE1vbmRheSByZXR1cm5zOgoKIyMgTWVhbiByZXZlcnNpb24KcnNpMl92YWx1ZXNfTW9uID0gUlNJKG1hcmtldGNsb3NlX3JldF9Nb24vMTAwLDIpCgojIyBUcmVuZCBmb2xsb3dpbmcKc21hLnNob3J0X01vbiA8LSBTTUEobWFya2V0Y2xvc2VfcmV0X01vbi8xMDAsIDIpCnNtYS5sb25nX01vbiA8LSAgU01BKG1hcmtldGNsb3NlX3JldF9Nb24vMTAwLCA1KQoKIyMgVm9sYXRpbGl0eSBpbiB3ZWVrbHkgYmFycyBvZiBkYWlseSByZXR1cm5zIG9uIE1vbmRheXMKIyBOb3RlIHRoYXQgd2UgaGFkIG11bHRpcGxpZWQgdGhlIGNoYW5nZXMgYnkgMTAwIHdoZW4gY2FsY3VsYXRpbmcgYG1hcmtldGNsb3NlX3JldGAKbGlicmFyeShxdWFudG1vZCkKcmV0LmxvZ19Nb24gPSBtYXJrZXRjbG9zZV9yZXRfTW9uLzEwMApoaXN0LnZvbF9Nb24gPSBydW5TRChyZXQubG9nX01vbiwgbiA9IDUpCnZvbC5yYW5rX01vbiA9IHBlcmNlbnQucmFuayhTTUEocGVyY2VudC5yYW5rKGhpc3Qudm9sX01vbiwgNTIpLCA1KSwgNTApCgpgYGAKCgoKYGBge3J9CiMjIFN3aXRjaGluZyBzdHJhdGVneSBmb3IgTW9uZGF5IHJldHVybnMKc2lnX01vbl9yZXQgPC0gIChMYWcoTGFnKGlpZih2b2wucmFua19Nb24gPiAwLjUwLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGlpZigocnNpMl92YWx1ZXNfTW9uIDwgNTApICwgMSwgLTEpLAogICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydF9Nb24gPCBzbWEubG9uZ19Nb24gfCAoKHJzaTJfdmFsdWVzX01vbiA+IDgwKSksIC0xLCAxKQopKSkpCgojIyBDdW11bGF0aXZlIFBOTCBwbG90cyBmb3IgcHJlZGljdGluZyBkYWlseSByZXR1cm5zIG9uIE1vbmRheXMKcmV0X01vbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cylbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0qc2lnX01vbl9yZXQKZXFfTW9uIDwtIChjdW1zdW0ocmV0X01vblstYygxLDIpXSkpCnhfdmFyX01vbiA8LSAoZGF0ZV90c1ttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSlbLWMoMSwyKV0KcGxvdChhcy5udW1lcmljKGVxX01vbil+eF92YXJfTW9uLHR5cGU9J2wnLHhsYWI9IlllYXIiLHlsYWI9IkN1bXVsYXRpdmUgcmV0dXJucyBvbiBNb25kYXlzIiwKICAgICB5bGltPWMoLTEwMDAwLDMwMDAwKSkKCmVxX29wdF9Nb24gPC0gYXMubnVtZXJpYyggY3Vtc3VtKCAocmV0X29wdFttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSlbLWMoMSwyKV0pICkKbGluZXMoZXFfb3B0X01vbiB+IHhfdmFyX01vbixjb2w9ImdyZWVuIikKCmVnX2dpdmVuU3RyIDwtIGFzLm51bWVyaWMoIGN1bXN1bSggKDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKVttYXJrZXRjbG9zZV93ZWVrZGF5ID09ICJNb24iXSlbLWMoMSwyKV0pICkKbGluZXMoZWdfZ2l2ZW5TdHIgfiB4X3Zhcl9Nb24sY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xKQpsZWdlbmQoeD0idG9wbGVmdCIsbGVnZW5kPWMoIk1vbmRheSBwcmVkaWN0aW9uIiwiQ29tYmluZWQgc3dpdGNoaW5nIiwiQnV5IGV2ZXJ5IE1vbmRheSAoR2l2ZW4pIiksCiAgICAgICBmaWxsPWMoImJsYWNrIiwiZ3JlZW4iLCJyZWQiKSkKYGBgCgoKYGBge3J9CiMjIE9wdGltaXppbmcgcGFyYW1ldGVycwoKIyBDcmVhdGUgc3Vic2V0IGFycmF5cyB0byB0ZXN0IGhvdyBtdWNoIHRoZXNlIHBhcmFtZXRlcnMgY2hhbmdlIG92ZXIgdGltZQojIEV2ZXJ5IDEwIHdlZWsgdXBkYXRlIG9mIHBhcmFtZXRlcnMKc3ViX3Nlci5sZW4uTW9uIDwtIDUKc3RhcnRfcG9pbnQuTW9uIDwtIDE0MApUbWF4X2FycmF5Lk1vbiA8LSBzZXEoc3RhcnRfcG9pbnQuTW9uLGxlbmd0aChtYXJrZXRjbG9zZV9yZXRfTW9uKSxzdWJfc2VyLmxlbi5Nb24pCgojIEdyaWQgc2VhcmNoIGZvciBwYXJhbWV0ZXJzIHRoYXQgbWF4aW1pemUgY3VtdWxhdGl2ZSByZXR1cm4gb3ZlciBzdWJzZXQgdGltZSBzZXJpZXMKIyBhbmQgc3RvcmUgdGhlIHByZWRpY3Rpb24gZm9yIG5leHQgZGF5CnJldHVybl9tYXgudGVzdC5Nb24gPC0gbWF0cml4KC1JbmYsbGVuZ3RoKFRtYXhfYXJyYXkuTW9uKSwxKQpwcmVkaWN0X2FycmF5LnRlc3QuTW9uIDwtIG1hdHJpeCgxLGxlbmd0aChUbWF4X2FycmF5Lk1vbiksMSkKbWF4X3Bhci50ZXN0Lk1vbiA8LSBtYXRyaXgoMCxsZW5ndGgoVG1heF9hcnJheS5Nb24pLDMpCmBgYAoKCmBgYHtyfQojIE9yIGRpcmVjdGx5IHJlYWQgZnJvbSBoZXJlCm1heF9wYXIudGVzdC5Nb24gPC0gcmVhZC5jc3YoIm1heF9wYXIudGVzdC5Nb24uY3N2IilbLGMoIlYxIiwiVjIiLCJWMyIpXQpgYGAKCgpgYGB7cn0KcHJpbnQoIlN0YXJ0aW5nIGdyaWQgc2VhcmNoLiBUaGlzIG1pZ2h0IHRha2UgYSB3aGlsZSAoNS0xMCBtaW51dGVzKSIpCmZvcmVhY2ggKGkgPSAgMTpsZW5ndGgoVG1heF9hcnJheS5Nb24pKSAlZG8lIHsKICBwcmludChwYXN0ZSgiVXBkYXRpbmcgdGhyZXNob2xkIHBhcmFtZXRlcnMgZm9yIHRoZSA1IHdlZWsgTW9uZGF5IHRpbWUgc2VyaWVzIixpLCJvZiIsbGVuZ3RoKFRtYXhfYXJyYXkuTW9uKSkpCiAgVG1heC5Nb24gPC0gVG1heF9hcnJheS5Nb25baV0KICBtYXJrZXRjbG9zZV9yZXRfTW9uLnRlc3QgPC0gbWFya2V0Y2xvc2VfcmV0X01vblsxOlRtYXguTW9uXQogICMgRGlmZmVyZW5jZXMgaW4gcmV0dXJucyBvbiBNb25kYXlzCiAgbWFya2V0Y2xvc2VfcmV0X3JldF9Nb24udGVzdCA8LSAxMDAqZGlmZigobWFya2V0Y2xvc2VfcmV0X01vbi50ZXN0KSlbLWMoMSldCiAgCiAgIyMgVm9sYXRpbGl0eSBpbiB3ZWVrbHkgcmV0dXJucwogIHJldC5sb2cudGVzdC5Nb24gPC0gbWFya2V0Y2xvc2VfcmV0X01vbi50ZXN0LzEwMAogIGhpc3Qudm9sLnRlc3QuTW9uIDwtIHJ1blNEKHJldC5sb2cudGVzdC5Nb24sIG4gPSA1KQogIHZvbC5yYW5rLnRlc3QuTW9uIDwtIHBlcmNlbnQucmFuayhTTUEocGVyY2VudC5yYW5rKGhpc3Qudm9sLnRlc3QuTW9uLCA1MiksIDUpLCA1MCkKICAKICAjIyBNZWFuIHJldmVyc2lvbgogIHJzaTJfdmFsdWVzLnRlc3QuTW9uID0gUlNJKG1hcmtldGNsb3NlX3JldF9Nb24udGVzdC8xMDAsMikKICAjIyBUcmVuZCBmb2xsb3dpbmcKICBzbWEuc2hvcnQudGVzdC5Nb24gPC0gIFNNQShtYXJrZXRjbG9zZV9yZXRfTW9uLnRlc3QvMTAwLCAyKQogIHNtYS5sb25nLnRlc3QuTW9uIDwtIFNNQShtYXJrZXRjbG9zZV9yZXRfTW9uLnRlc3QvMTAwLCA1KQogIAogICMgR3JpZCBzZWFyY2ggZm9yIE1vbmRheSBwYXJhbWV0ZXJzCiAgZm9yZWFjaCAodm9sLnJhbmsuTW9uX01JTiA9IHNlcSgwLjMsMC44LDAuMDEpKSAlZG8lIHsKICAgIGZvcmVhY2ggKHJzaTJfdmFsdWVzLk1vbl9NQVggPSBzZXEoMzAsODAsMikpICVkbyUgewogICAgICBmb3IgKHJzaTJfdmFsdWVzLk1vbl9NSU4gaW4gNjApeyAjc2VxKDMwLDgwLDUpKSAgJWRvJXsgIyBkb2Vzbid0IGNoYW5nZQogICAgICAgIHNpZy50ZXN0Lk1vbiA8LSAgKGlpZih2b2wucmFuay50ZXN0Lk1vbiA+IHZvbC5yYW5rLk1vbl9NSU4sIAogICAgICAgICAgICAgICAgICAgICAgICAgIGlpZigocnNpMl92YWx1ZXMudGVzdC5Nb24gPCByc2kyX3ZhbHVlcy5Nb25fTUFYKSAsIDEsIC0xKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpaWYoc21hLnNob3J0LnRlc3QuTW9uIDwgc21hLmxvbmcudGVzdC5Nb24gfCAoKHJzaTJfdmFsdWVzLnRlc3QuTW9uID4gcnNpMl92YWx1ZXMuTW9uX01JTikpLCAtMSwgMSkKICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICApKQogICAgICAgIHByZWRpY3QudGVzdC5Nb24gPC0gdGFpbChzaWcudGVzdC5Nb24sMSkKICAgICAgICBzaWcudGVzdC5Nb24gPC0gTGFnKExhZyhzaWcudGVzdC5Nb24pKQogICAgICAgIHJldC50ZXN0Lk1vbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3h0cylbbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl0qc2lnLnRlc3QuTW9uCiAgICAgICAgI3JldC50ZXN0Lk1vbiA8LSAxMDAwKm1vbWVudHVtKG1hcmtldGNsb3NlX3JldF9Nb24udGVzdCkqc2lnLnRlc3QuTW9uCiAgICAgICAgCiAgICAgICAgcmV0dXJuX3RvdGFsLnRlc3QuTW9uIDwtIHRhaWwoY3Vtc3VtKHJldC50ZXN0Lk1vblstYygxLDIpXSksMSkKICAgICAgICAKICAgICAgICAjT3B0aW1pemUgY3VtdWxhdGl2ZSByZXR1cm4gb24gTW9uZGF5cwogICAgICAgIGlmIChyZXR1cm5fdG90YWwudGVzdC5Nb24gPiByZXR1cm5fbWF4LnRlc3QuTW9uW2ldKSB7CiAgICAgICAgICAKICAgICAgICAgIHJldHVybl9tYXgudGVzdC5Nb25baV0gPC0gcmV0dXJuX3RvdGFsLnRlc3QuTW9uCiAgICAgICAgICBtYXhfcGFyLnRlc3QuTW9uW2ksMV0gPC0gdm9sLnJhbmsuTW9uX01JTgogICAgICAgICAgbWF4X3Bhci50ZXN0Lk1vbltpLDJdIDwtIHJzaTJfdmFsdWVzLk1vbl9NQVgKICAgICAgICAgIG1heF9wYXIudGVzdC5Nb25baSwzXSA8LSByc2kyX3ZhbHVlcy5Nb25fTUlOCiAgICAgICAgICBwcmVkaWN0X2FycmF5LnRlc3QuTW9uW2ldIDwtIHByZWRpY3QudGVzdC5Nb24KICAgICAgICAgIHNpZy50ZXN0Lk1vbi5tYXggPC0gc2lnLnRlc3QuTW9uCiAgICAgICAgICAKICAgICAgICB9CiAgICAgICAgCiAgICAgIH0KICAgICAgCiAgICB9CiAgICAKICB9Cn0KCgojd3JpdGUuY3N2KG1heF9wYXIudGVzdC5Nb24sIm1heF9wYXIudGVzdC5Nb24uY3N2IikKYGBgCgpgYGB7cn0KcHJpbnQobWF4X3Bhci50ZXN0Lk1vbikKYGBgCgpgYGB7cn0KIyBXZSB1c2UgdGhlIG9wdGltYWwgcGFyYW1ldGVycyBmb3IgTW9uZGF5IHVwZGF0ZSAoMC40NCw2Miw2MCksIHRvIGNvcnJlY3QgdGhlIGVhcmxpZXIgbW9kZWwKIyMgU3dpdGNoaW5nIHN0cmF0ZWd5IGZvciBNb25kYXkgcmV0dXJucwpzaWdfb3B0Lk1vbiA8LSAgTGFnKExhZyhpaWYodm9sLnJhbmtfTW9uID4gMC40NCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKChyc2kyX3ZhbHVlc19Nb24gPCA2MikgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWlmKHNtYS5zaG9ydF9Nb24gPCBzbWEubG9uZ19Nb24gfCAoKHJzaTJfdmFsdWVzX01vbiA+IDYwKSksIC0xLCAxKQopKSkKCgojI1Bsb3Qgb2YgY3VtdWxhdGl2ZSByZXR1cm5zIG9uIE1vbmRheXMgYWZ0ZXIgYW5kIGJlZm9yZSB1cGRhdGUKcGxvdChhcy5udW1lcmljKGVxX01vbil+eF92YXJfTW9uLHR5cGU9J2wnLHhsYWI9IlllYXIiLHlsYWI9IkN1bXVsYXRpdmUgcmV0dXJucyBvbiBNb25kYXlzIiwKICAgICB5bGltPWMoLTEwMDAwLDMwMDAwKSkKbGluZXMoZXFfb3B0X01vbiB+IHhfdmFyX01vbixjb2w9ImdyZWVuIikKbGluZXMoZWdfZ2l2ZW5TdHIgfiB4X3Zhcl9Nb24sY29sPSJyZWQiKQoKcmV0X01vbl9vcHQgPC0gMTAwMCptb21lbnR1bShtYXJrZXRjbG9zZV94dHMpW21hcmtldGNsb3NlX3dlZWtkYXkgPT0gIk1vbiJdKnNpZ19vcHQuTW9uCmVxX01vbl9vcHQgPC0gYXMubnVtZXJpYyggY3Vtc3VtKCAocmV0X01vbl9vcHQpWy1jKDEsMildKSApCmxpbmVzKGVxX01vbl9vcHQgfiB4X3Zhcl9Nb24sY29sPSJibHVlIikKCmdyaWQoY29sPTEsbHdkPTEpCmxlZ2VuZCh4PSJ0b3BsZWZ0IixsZWdlbmQ9YygiNSB3ZWVrIE1vbmRheSB1cGRhdGUgKyA1IGRheSB1cGRhdGUiLCJNb25kYXkgcHJlZGljdGlvbiIsIjUgZGF5IHVwZGF0ZSIsIkJ1eSBldmVyeSBNb25kYXkgKEdpdmVuKSIpLAogICAgICAgZmlsbD1jKCJibHVlIiwiYmxhY2siLCJncmVlbiIsInJlZCIpKQoKCmBgYAoKYGBge3J9CiMgQ29ycmVjdGluZyB0aGUgZWFybGllciBwbG90cwpzaWdfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbiA8LSBzaWdfb3B0X29wdGltYWwKc2lnX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb25bbWFya2V0Y2xvc2Vfd2Vla2RheSA9PSAiTW9uIl1bLWMoMSwyKV0gPC0gc2lnX29wdC5Nb25bLWMoMSwyKV0KCnJldF9vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uIDwtIDEwMDAqbW9tZW50dW0obWFya2V0Y2xvc2VfeHRzKVstYygxLDIpXSpzaWdfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbgplcV9vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uIDwtIGFzLm51bWVyaWMoIGN1bXN1bSggKHJldF9vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uKSApKQpgYGAKCgpgYGB7cn0KI0N1bXVsYXRpdmUgUE5MIHBsb3RzIGZvciBjb21wbGV0ZSBzZXJpZXMgYWZ0ZXIgbW9uZGF5IGNvcnJlY3Rpb24KcGxvdChjKE5BLGVxX29wdF9vcHRpbWFsLk1vbkNvcnJlY3Rpb24pfmRhdGVfdHNbLWMoMSldLGNvbD0ib3JhbmdlIix0eXBlPSdsJywgeWxpbT1jKDAsMTQwMDAwKSx4bGFiPSAiWWVhciIsIHlsYWIgPSAiQ3VtdWxhdGl2ZSBQTkwiKQpsaW5lcyhjb3JlZGF0YShlcV9vcHRfb3B0aW1hbCl+ZGF0ZV90c1stYygxKV0sY29sPSJibHVlIikKbGluZXMoY29yZWRhdGEoZXFfb3B0KX5kYXRlX3RzWy1jKDEpXSxjb2w9ImdyZWVuIikKbGluZXMoY29yZWRhdGEoZXEpfmRhdGVfdHNbLWMoMSldKQpsaW5lcyhkYXRhX2luJENVTVNVTVstYygxKV1+ZGF0ZV90c1stYygxKV0sY29sPSJyZWQiKQpncmlkKGNvbD0xLGx3ZD0xKQpsZWdlbmQoeD0idG9wbGVmdCIsbGVnZW5kPWMoIjVkYXkgdXBkYXRlIHN3IiwiRml4ZWQgcGFyIHN3aXRjaGluZyIsIkJ1eSBEYWlseSAoR2l2ZW4pIiwiT3B0aW1hbCIsIk9wdGltYWwrTW9uZGF5IHVwZGF0ZXMiKSwKICAgICAgIGZpbGw9YygiZ3JlZW4iLCJibGFjayIsInJlZCIsImJsdWUiLCJvcmFuZ2UiKSkKYGBgCgpgYGB7cn0KIyBTaW1pbGFybHkgd2UgY2FuIGNoZWNrIGZvciB3ZWVrZGF5IHBhdHRlcm5zIG9uIG90aGVyIGRheXMgYW5kIGFsc28gcG90ZW50aWFsIG1vbnRobHkgb3IgcXVhcnRlcmx5IHBhdHRlcm5zCgojIyBTaW5jZSBuZXh0IGRheSBvbiB3aGljaCBwcmVkaWN0aW9uIGlzIG5lZWRlZCBhbHNvIGZhbGxzIG9uIGEgTW9uZGF5LCB3ZSBjYW4gYWxzbyB2ZXJpZnkgdGhlIHByZWRpY3Rpb24gZnJvbSBNb25kYXkgc2VyaWVzIG9ubHk6Cmxhc3R3ZWVrX3Jvdy5Nb24gPC0gbGVuZ3RoKG1hcmtldGNsb3NlX3JldF9Nb24pCnByZWRpY3Rpb25fZml4ZWQuTW9uIDwtIGlpZih2b2wucmFua19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPiAwLjUwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlpZihyc2kyX3ZhbHVlc19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPCA1MCAsIDEsIC0xKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlpZigoc21hLnNob3J0X01vbltsYXN0d2Vla19yb3cuTW9uXSA8IHNtYS5sb25nX01vbltsYXN0d2Vla19yb3cuTW9uXSkgfCAocnNpMl92YWx1ZXNfTW9uW2xhc3R3ZWVrX3Jvdy5Nb25dID4gODApICwgLTEsIDEpCikKCnByZWRpY3Rpb25fb3B0IDwtIGlpZih2b2wucmFua19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPiAwLjQ0LCAKICAgICAgICAgICAgICAgICAgICAgIGlpZihyc2kyX3ZhbHVlc19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPCA2MiAgLCAxLCAtMSksCiAgICAgICAgICAgICAgICAgICAgICBpaWYoKHNtYS5zaG9ydF9Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPCBzbWEubG9uZ19Nb25bbGFzdHdlZWtfcm93Lk1vbl0pICB8IChyc2kyX3ZhbHVlc19Nb25bbGFzdHdlZWtfcm93Lk1vbl0gPiA2MCksIC0xLCAxKQopCgojIEFnYWluIGZpeGVkIHBhcmFtZXRlciBzd2l0Y2hpbmcgZmFpbHMgdG8gcHJlZGljdCBjb3JyZWN0bHkgYnV0IDIgd2VlayBwYXJhbWV0ZXIgdXBkYXRlIGRvZXMgY29ycmVjdGx5IHByZWRpY3QgLTEKcHJpbnQocGFzdGUwKCJQcmVkaWN0aW9uIGZvciBuZXh0IGRheSAoMiB3ZWVrIHBhcmFtZXRlciB1cGRhdGUpOiAgIixwcmVkaWN0aW9uX29wdCkpCgpgYGAKCmBgYHtyfQpDb21wYXJlIGN1bXVsYXRpdmUgUE5MIGF0IHRoZSBlbmQgYWZ0ZXIgTW9uZGF5IGNvcnJlY3Rpb24KcHJpbnQoZGF0YS5mcmFtZSgnR2l2ZW4gU3RyYXRlZ3knPXRhaWwoZGF0YV9pbiRDVU1TVU1bLWMoMSldKSwnRml4UGFyU3dpdGNoaW5nJz10YWlsKGFzLnZlY3RvcihlcSkpLCc1ZGF5VXBkYXRlU3cnPXRhaWwoYXMudmVjdG9yKGVxX29wdCkpLCdPcHRpbWFsJz10YWlsKGFzLnZlY3RvcihlcV9vcHRfb3B0aW1hbCkpLCdPcHRpbWFsJk1vblVwZGF0ZSc9dGFpbChhcy52ZWN0b3IoZXFfb3B0X29wdGltYWwuTW9uQ29ycmVjdGlvbikpLHJvdy5uYW1lcyA9IGFzLmNoYXJhY3Rlcih0YWlsKGRhdGVfdHMpKSkpCgp3cml0ZS5jc3YoY2JpbmQoInNpZ19vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uIj1hcy5udW1lcmljKHNpZ19vcHRfb3B0aW1hbC5Nb25Db3JyZWN0aW9uKSwic2lnX29wdF9vcHRpbWFsIj1hcy5udW1lcmljKHNpZ19vcHRfb3B0aW1hbCksInNpZ19vcHQiPWFzLm51bWVyaWMoc2lnX29wdCksJ3NpZyc9YXMubnVtZXJpYyhzaWcpKSwidHJhZGluZ19zaWduYWxzLmNzdiIscXVvdGU9RkFMU0Uscm93Lm5hbWVzID0gZGF0ZV90cykKYGBgCgo=